source: cpp/frams/util/extvalue.cpp @ 121

Last change on this file since 121 was 121, checked in by sz, 7 years ago

updated file headers and makefiles

  • Property svn:eol-style set to native
File size: 17.8 KB
Line 
1// This file is a part of the Framsticks GDK.
2// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
3// Refer to http://www.framsticks.com/ for further information.
4
5#include "extvalue.h"
6#include <frams/param/param.h>
7#include "sstringutils.h"
8#include <ctype.h>
9#include <frams/vm/classes/collectionobj.h>
10#include <frams/vm/classes/3dobject.h>
11#include <common/nonstd_math.h>
12#include <common/Convert.h>
13
14#ifndef NO_BARRIER
15#include <frams/simul/barrier.h>
16#include <common/threads.h>
17#endif
18
19#ifdef MULTITHREADED
20#include <pthread.h>
21//this lock only protects against ref.counter corruption caused by concurrent reads.
22//read/write conficts and nonatomicity are handled by BarrierObject (at least in theory ;-))
23static pthread_mutex_t extobject_ref_lock=PTHREAD_MUTEX_INITIALIZER;
24#define REF_LOCK pthread_mutex_lock(&extobject_ref_lock)
25#define REF_UNLOCK pthread_mutex_unlock(&extobject_ref_lock)
26#else
27#define REF_LOCK
28#define REF_UNLOCK
29#endif
30
31void ExtObject::incref() const
32{
33if (subtype&1)
34        {
35        REF_LOCK;
36        dbobject->refcount++;
37        REF_UNLOCK;
38        }
39}
40
41void ExtObject::decref() const
42{
43if (subtype&1)
44        {
45        REF_LOCK;
46        bool destroy=!--dbobject->refcount;
47        REF_UNLOCK;
48        //another thread can now access the object while we are deleting it
49        //but this is not a bug since we only guarantee read/read safety
50        if (destroy) delete dbobject;
51        }
52}
53
54bool ExtObject::makeUnique()
55{
56if (!(subtype&1)) return false;
57if (dbobject->refcount==1) return false;
58VectorObject* v=VectorObject::fromObject(*this);
59if (v)
60        {
61        VectorObject* n=new VectorObject;
62        n->data.setSize(n->data.size());
63        for(int i=0;i<v->data.size();i++)
64                {
65                ExtValue *x=(ExtValue*)v->data(i);
66                n->data.set(i,x?new ExtValue(*x):NULL);
67                }
68        operator=(n->makeObject());
69        return true;
70        }
71return false;
72}
73
74void* ExtObject::getTarget(const char* classname, bool through_barrier) const
75{
76if (!strcmp(interfaceName(),classname))
77        return getTarget();
78#ifndef NO_BARRIER
79if (through_barrier)
80        {
81        BarrierObject *bo=BarrierObject::fromObject(*this);
82        if (bo)
83                return bo->getSourceObject().getTarget(classname);
84        }
85#endif
86return NULL;
87}
88
89
90SString ExtObject::toString() const
91{
92if (isEmpty()) return SString("<empty object>");
93Param tmp_param;
94ParamInterface *p=getParamInterface(tmp_param);
95int tostr=p->findId("toString");
96if (tostr>=0)
97        {
98        return SString(p->getString(tostr));
99        }
100else
101        {
102        SString tmp("<");
103        tmp+=p->getName();
104        tmp+=SString::sprintf(" object at %p>",object?object:paraminterface);
105        return tmp;
106        }
107}
108
109THREAD_LOCAL_DEF(ExtObject::Serialization,ExtObject::serialization);
110
111void ExtObject::Serialization::begin()
112{
113if (level==0)
114        refs.clear();
115level++;
116}
117
118int ExtObject::Serialization::add(const ExtObject &o)
119{
120if (o.isEmpty()) return -1;
121for(int i=0;i<(int)refs.size();i++)
122        {
123        ExtObject& r=refs[i];
124        if (r==o) return i;
125        }
126refs.push_back(o);
127return -1;
128}
129
130void ExtObject::Serialization::replace(const ExtObject& o,const ExtObject& other)
131{
132if (o.isEmpty()) return;
133for(int i=0;i<(int)refs.size();i++)
134        {
135        ExtObject& r=refs[i];
136        if (r==o)
137                {
138                r=other;
139                return;
140                }
141        }
142}
143
144void ExtObject::Serialization::remove(const ExtObject& o)
145{
146if (o.isEmpty()) return;
147for(int i=0;i<(int)refs.size();i++)
148        {
149        ExtObject& r=refs[i];
150        if (o==r) refs.erase(refs.begin()+i);
151        }
152}
153
154const ExtObject* ExtObject::Serialization::get(int ref)
155{
156if (ref<0) return NULL;
157if (ref>=(int)refs.size()) return NULL;
158return &refs[ref];
159}
160
161void ExtObject::Serialization::end()
162{
163level--;
164if (level==0)
165        refs.clear();
166}
167
168SString ExtObject::serialize_inner() const
169{
170int ref=tlsGetRef(serialization).add(*this);
171if (ref>=0)
172        return SString::sprintf("^%d",ref);
173
174if (isEmpty()) return SString();
175VectorObject *vec=VectorObject::fromObject(*this);
176if (vec)
177        return vec->serialize();
178DictionaryObject *dic=DictionaryObject::fromObject(*this);
179if (dic)
180        return dic->serialize();
181Param tmp_param;
182ParamInterface *p=getParamInterface(tmp_param);
183int m=p->findId("toVector");
184if (m<0)
185        m=p->findId("toDictionary");
186if (m>=0)
187        {
188        ExtObject o(p->getObject(m));
189        SString ret=SString(interfaceName())+o.serialize();
190        return ret;
191        }
192m=p->findId("toString");
193if (m>=0)
194        {
195        SString str=p->getString(m);
196        sstringQuote(str);
197        SString ret=SString(interfaceName())+"\""+str+"\"";
198        return ret;
199        }
200
201tlsGetRef(serialization).remove(*this);//undo nonserializable reference
202SString ret=interfaceName();
203ret+=SString::sprintf("<%p>",object?object:paraminterface);
204return ret;
205}
206
207SString ExtObject::serialize() const
208{
209tlsGetRef(serialization).begin();
210SString ret=serialize_inner();
211tlsGetRef(serialization).end();
212return ret;
213}
214
215///////////////////////////////////////
216
217void ExtValue::set(const ExtValue& src)
218{
219switch(src.type)
220        {
221        case TString: sets(src.sdata()); break;
222        case TInt: seti(src.idata()); break;
223        case TDouble: setd(src.ddata()); break;
224        case TObj: seto(src.odata()); break;
225        default:type=src.type; break;
226        }
227}
228
229void ExtValue::setEmpty()
230{
231switch(type)
232        {
233#ifdef EXTVALUEUNION
234        case TString: sdata().~SString(); break;
235        case TObj: odata().~ExtObject(); break;
236#else
237        case TString: delete s; break;
238        case TObj: delete o; break;
239#endif
240        default:;
241        }
242type=TUnknown;
243}
244
245static long longsign(long x)
246{
247if (x<0) return -1;
248if (x>0) return 1;
249return 0;
250}
251
252long ExtValue::compare(const ExtValue& src) const
253{
254if (type==TUnknown)
255        {
256        if (src.type==TDouble)
257                return (src.getDouble()!=0.0);
258        if (src.type==TString)
259                return 1;
260        return src.getInt()?1:0;
261        }
262else if (src.type==TUnknown)
263        {
264        if (type==TDouble)
265                return (getDouble()!=0.0);
266        if (type==TString)
267                return 1;
268        return getInt()?1:0;
269        }
270switch(type)
271        {
272        case TInt:
273        {
274        long t=src.getInt();
275        if (idata()>0)
276                {if (t>0) return longsign(idata()-t); else return +1;}
277        else
278                {if (t<=0) return longsign(idata()-t); else return -1;}
279        }
280        case TDouble:
281                {
282                double t=ddata()-src.getDouble();
283                if (t<0) return -1;
284                else if (t>0) return 1;
285                return 0;
286                }
287        case TString:
288        {
289        SString t=src.getString();
290        SString& t2=sdata();
291        const char* s1=(const char*)t2;
292        const char* s2=(const char*)t;
293        return longsign(strcmp(s1,s2));
294        }
295        case TObj:
296        {
297        if (src.type==TObj)
298                return !(odata()==src.odata());
299        return 1;
300        }
301        default:;
302        }
303return 1;
304}
305
306int ExtValue::operator==(const ExtValue& src) const
307{
308if (type!=src.type) return 0;
309switch(type)
310        {
311        case TInt: return idata()==src.idata();
312        case TDouble: return ddata()==src.ddata();
313        case TString: return sdata()==src.sdata();
314        case TObj: return odata()==src.odata();
315        default:;
316        }
317return 1;
318}
319
320void ExtValue::operator+=(const ExtValue& src)
321{
322switch(type)
323        {
324        case TInt: idata()+=src.getInt(); break;
325        case TDouble: ddata()+=src.getDouble(); break;
326        case TString: sdata()+=src.getString(); break;
327        case TObj:
328                {
329                VectorObject *vec=VectorObject::fromObject(getObject());
330                VectorObject *vec2=VectorObject::fromObject(src.getObject());
331                if (vec && vec2)
332                        {
333                        for(int i=0;i<vec2->data.size();i++)
334                                {
335                                ExtValue *s=(ExtValue*)vec2->data(i);
336                                ExtValue *d=s?new ExtValue(*s):NULL;
337                                vec->data+=d;
338                                }
339                        }
340                }
341        default:;
342        }
343}
344
345void ExtValue::operator-=(const ExtValue& src)
346{
347switch(type)
348        {
349        case TInt: idata()-=src.getInt(); break;
350        case TDouble: ddata()-=src.getDouble(); break;
351        default:;
352        }
353}
354
355void ExtValue::operator*=(const ExtValue& src)
356{
357switch(type)
358        {
359        case TInt: idata()*=src.getInt(); break;
360        case TDouble: ddata()*=src.getDouble(); break;
361        case TString:
362        {
363        SString t;
364        for(int n=src.getInt();n>0;n--)
365                t+=getString();
366        setString(t);
367        break;
368        }
369        case TObj:
370                {
371                VectorObject *vec=VectorObject::fromObject(getObject());
372                if (vec)
373                        {
374                        int n=src.getInt();
375                        int orig_size=vec->data.size();
376                        if (n<=0)
377                                {vec->clear();return;}
378                        for(;n>1;n--)
379                                {
380                                for(int i=0;i<orig_size;i++)
381                                        {
382                                        ExtValue *s=(ExtValue*)vec->data(i);
383                                        ExtValue *d=s?new ExtValue(*s):NULL;
384                                        vec->data+=d;
385                                        }
386                                }
387                        }
388                break;
389                }
390        default:;
391        }
392}
393
394#include <common/framsg.h>
395/*#include "fpu_control.h"
396#include <signal.h>
397
398static int fpuexception;
399void mathhandler(int sig)
400{
401printf("fpu exception!\n");
402fpuexception=1;
403signal(SIGFPE,SIG_IGN);
404} */
405
406void ExtValue::operator/=(const ExtValue& src)
407{
408switch(type)
409        {
410        case TInt:
411        { int a=src.getInt();
412//              idata()/=src.getInt();
413        if (a) idata()/=a;
414        else {FMprintf("ExtValue","divide",FMLV_CRITICAL,"%d/0",idata()); setInvalid();}
415        }
416                break;
417
418        case TDouble:
419                {
420                double d=src.getDouble();
421                if (d==0.0)
422                        {
423                        FMprintf("ExtValue","divide",FMLV_CRITICAL,"%s/0.0",(const char*)getString());
424                        setInvalid();
425                        }
426                else
427                        {
428                        fpExceptDisable();
429                        double tmp=ddata()/d;
430                        if (!finite(tmp))
431                                { FMprintf("ExtValue","divide",FMLV_CRITICAL,"overflow %s/%s",(const char*)getString(),(const char*)src.getString()); setInvalid(); }
432                        else
433                                ddata()=tmp;
434                        // niby dobrze ale lepiej byloby to robic bardziej systematycznie a nie tylko w dzieleniu?
435                        //if (isnan(ddata())) //http://www.digitalmars.com/d/archives/c++/Traping_divide_by_zero_5728.html
436                        //        { FMprintf("ExtValue","divide",FMLV_ERROR,"not-a-number",(const char*)getString()); setInvalid(); }
437                        fpExceptEnable();
438                        }
439                }
440                break;
441
442        default:;
443        }
444}
445
446SString ExtValue::format(SString& fmt,const ExtValue **values,int count)
447{
448SString ret;
449// "..........%.........%..........%........"
450//  ^_cur     ^_next
451//  ^^^^^^^^^^___sub
452//
453// "..........%.........%..........%........"
454//            ^-cur     ^-next
455//            ^^^^^^^^^^___sub
456const char* begin=(const char*)fmt, *end=begin+fmt.len(), *curr=begin;
457int type=0;
458
459class Args
460{
461const ExtValue **values;
462int count;
463int arg;
464public:
465Args(const ExtValue **v,int c):values(v),count(c),arg(0) {}
466bool finished() {return arg>=count;}
467const ExtValue *getNext() {const ExtValue *ret=NULL; if ((arg<count)&&values[arg]) ret=values[arg]; arg++; return ret;}
468};
469Args args(values,count);
470
471while(curr<end)
472        {
473        const char* next=strchr(curr,'%');
474        if (!next) next=end; else if ((next==curr)&&(curr>begin))
475                {next=strchr(next+1,'%'); if (!next) next=end;}
476        type=0;
477        if (curr>begin)
478                {
479                type=0;
480                for(const char* t=curr;t<next;t++)
481                        switch(*t)
482                                {
483                                case 'd': case 'x': case 'X': case 'u': case 'p': case 'c': type='d'; t=next; break;
484                                case 'f': case 'g': case 'e': type='f'; t=next; break;
485                                case 's': type='s'; t=next; break;
486                                case 't': case 'T': type=*t; t=next; break;
487                                case '%': if (t>begin) {type=*t; t=next;} break;
488                                }
489                }
490        if (curr>begin) curr--;
491        const ExtValue *a;
492        if (args.finished() && (type!=0) && (type!='%'))
493                {
494                ret+=fmt.substr(curr-begin);
495                break;
496                }
497        SString sub=fmt.substr(curr-begin,next-curr);
498        switch(type)
499                {
500                case 'd': a=args.getNext(); ret+=SString::sprintf((const char*)sub,a?a->getInt():0); break;
501                case 'f': a=args.getNext(); ret+=SString::sprintf((const char*)sub,a?a->getDouble():0); break;
502                case 's': {a=args.getNext(); SString tmp; if (a) tmp=a->getString(); ret+=SString::sprintf((const char*)sub,(const char*)tmp);} break;
503                case 't': case 'T':
504                        {
505                        a=args.getNext();
506                        time_t ti=a?a->getInt():0;
507                        struct tm tm=Convert::localtime(ti);
508                        SString timtxt;
509                        if (type=='T')
510                                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);
511                        else
512                                timtxt=Convert::asctime(tm).c_str();
513                        ret+=timtxt;
514                        ret+=sub.substr(2);
515                        }
516                        break;
517                case '%': ret+='%'; ret+=sub.substr(2); break;
518                case 0: ret+=sub; break;
519                }
520        curr=next+1;
521        }
522return ret;
523}
524
525
526void ExtValue::operator%=(const ExtValue& src)
527{
528switch(type)
529        {
530        case TInt: idata()=idata()%src.getInt(); break;
531        case TDouble: ddata()=fmod(ddata(),src.getDouble()); break;
532
533        case TString:
534        {
535        VectorObject *vec=VectorObject::fromObject(src.getObject());
536        if (vec)
537                sdata()=format(sdata(),(const ExtValue**)&vec->data.getref(0),vec->data.size());
538        else
539                {const ExtValue *ptr=&src; sdata()=ExtValue::format(sdata(),&ptr,1);}
540        }
541        break;
542
543        default:;
544        }
545}
546
547long ExtValue::getInt() const
548{
549switch(type)
550        {
551        case TInt: return idata();
552        case TDouble: return (int)ddata();
553        case TString:
554        {
555        const char* s=(const char*)sdata();
556        if ((s[0]=='0')&&(s[1]=='x'))
557                {
558                long val;
559                sscanf(s+2,"%lx",&val);
560                return val;
561                }
562        else
563                {
564                if (strchr(s,'e')||(strchr(s,'E')))
565                        return (long)atof(s);
566                else
567                        return atol(s);
568                }
569        }
570        case TObj: return (long)odata().param;
571        default:;
572        }
573return 0;
574}
575double ExtValue::getDouble() const
576{
577switch(type)
578        {
579        case TDouble: return ddata();
580        case TInt: return (double)idata();
581        case TString:
582        {
583        const char* s=(const char*)sdata();
584        if ((s[0]=='0')&&(s[1]=='x'))
585                {
586                long val;
587                sscanf(s+2,"%lx",&val);
588                return val;
589                }
590        else
591                return atof(s);
592        }
593        case TObj: return (double)(long)odata().param;
594        default:;
595        }
596return 0.0;
597}
598SString ExtValue::getString() const
599{
600switch(type)
601        {
602        case TString: return sdata();
603        case TInt: return SString::valueOf(idata());
604        case TDouble: return SString::valueOf(ddata());
605        case TObj: return odata().toString();
606        case TInvalid:  return SString("invalid");
607        default: return SString("null");
608        }
609}
610
611const SString* ExtValue::getStringPtr() const
612{
613if (type==TString)
614        return &sdata();
615return NULL;
616}
617
618SString ExtValue::serialize() const
619{
620switch(type)
621        {
622        case TString:
623                {
624                SString q=sdata();
625                sstringQuote(q);
626                return SString("\"")+q+SString("\"");
627                }
628        case TInt:
629                return SString::valueOf(idata());
630        case TDouble:
631                return SString::valueOf(ddata());
632        case TObj:
633                return odata().serialize();
634        case TInvalid:
635                return SString("invalid");
636        default:
637                return SString("null");
638        }
639}
640
641//returns the first character after the parsed number or NULL if not a number
642const char* ExtValue::parseNumber(const char* in)
643{
644if (isdigit(*in)||((*in=='-')&&(isdigit(in[1]))))
645        {
646        const char* p=in;
647        if (*p=='-') p++;
648        while(isdigit(*p)) p++;
649        bool fp=false;
650        if ((*p=='.') && isdigit(p[1]))
651                {
652                p++;
653                while(isdigit(*p)) p++;
654                fp=true;
655                }
656        if (((*p=='e')||(*p=='E')) && (isdigit(p[1]) || (((p[1]=='-') || (p[1]=='+')) && isdigit(p[2]))))
657                {
658                p++;
659                if ((*p=='-')||(*p=='+')) p++;
660                while(isdigit(*p)) p++;
661                fp=true;
662                }
663
664        if (fp)
665                {
666                setDouble(atof(in));
667                return p;
668                }
669        else
670                {
671                setInt(atol(in));
672                return p;
673                }
674        }
675return NULL;
676}
677
678PtrListTempl<ParamInterface*> ExtValue::deserializable_classes;
679
680void ExtValue::initDeserializableClasses()
681{
682deserializable_classes+=&Pt3D_Ext::getStaticParam();
683deserializable_classes+=&Orient_Ext::getStaticParam();
684}
685
686ParamInterface *ExtValue::findDeserializableClass(const char* name)
687{
688FOREACH(ParamInterface*,cls,deserializable_classes)
689        if (!strcmp(cls->getName(),name))
690                return cls;
691return NULL;
692}
693
694static const char* skipWord(const char* in)
695{
696while(isalpha(*in)||(*in=='_'))
697        in++;
698return in;
699}
700
701//returns the first character after the parsed portion or NULL if invalid format
702const char* ExtValue::deserialize_inner(const char* in)
703{
704const char* ret=parseNumber(in);
705if (ret)
706        return ret;
707else if (*in=='\"')
708        {
709        ret=skipQuoteString(in+1,NULL);
710        SString s(in+1,ret-(in+1));
711        sstringUnquote(s);
712        setString(s);
713        if (*ret=='\"')
714                return ret+1;
715        else
716                return NULL;
717        }
718else if (*in=='[')
719        {
720        VectorObject *vec=new VectorObject;
721        ExtObject o(&VectorObject::par,vec);
722        tlsGetRef(ExtObject::serialization).add(o);
723        const char* p=in+1;
724        ExtValue tmp;
725        while(*p)
726                {
727                if (*p==']') {p++;break;}
728                ret=tmp.deserialize(p);
729                if (ret)
730                        {
731                        vec->data+=new ExtValue(tmp);
732                        p=ret;
733                        if (*p==',') p++;
734                        }
735                else
736                        {
737                        p=NULL;
738                        break;
739                        }
740                }
741        setObject(o);
742        return p;
743        }
744else if (*in=='{')
745        {
746        DictionaryObject *dic=new DictionaryObject;
747        ExtObject o(&DictionaryObject::par,dic);
748        tlsGetRef(ExtObject::serialization).add(o);
749        const char* p=in+1;
750        ExtValue args[2]/*={value,key}*/, dummy_ret;
751        while(*p)
752                {
753                if (*p=='}') {p++;break;}
754                ret=args[1].deserialize(p);
755                if ((!ret)||(args[1].getType()!=TString)) {p=NULL;break;}
756                p=ret;
757                if (*p!=':') {p=NULL;break;}
758                p++;
759                ret=args[0].deserialize(p);
760                if (!ret) {p=NULL;break;}
761                p=ret;
762                dic->p_set(args,&dummy_ret);
763                if (*p==',') p++;
764                }
765        setObject(o);
766        return p;
767        }
768else if (!strncmp(in,"null",4))
769        {
770        setEmpty();
771        return in+4;
772        }
773else if (!strncmp(in,"invalid",9))
774        {
775        setInvalid();
776        return in+9;
777        }
778else if (*in=='<')
779        { //unserializable object
780        setInvalid();
781        while(*in)
782                if (*in=='>')
783                        return in+1;
784                else in++;
785        return in;
786        }
787else if (*in=='^')
788        {
789        in++;
790        ExtValue ref;
791        ret=ref.parseNumber(in);
792        if (ret && (ref.getType()==TInt))
793                {
794                const ExtObject* o=tlsGetRef(ExtObject::serialization).get(ref.getInt());
795                if (o)
796                        {
797                        setObject(*o);
798                        return ret;
799                        }
800                }
801        return NULL;
802        }
803else if ((ret=skipWord(in))&&(ret!=in))
804        {
805        SString clsname(in,ret-in);
806        ExtValue tmp;
807        ret=tmp.deserialize(ret);
808        ParamInterface *cls=findDeserializableClass(clsname);
809        if (cls && (tmp.getType()!=TUnknown) && (tmp.getType()!=TInvalid))
810                {
811                VectorObject *vec=VectorObject::fromObject(tmp.getObject());
812                if (vec)
813                        {
814                        int m=cls->findId("newFromVector");
815                        if (m>=0)
816                                {
817                                cls->call(m,&tmp,this);
818                                tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject());
819                                return ret;
820                                }
821                        }
822                DictionaryObject *dic=DictionaryObject::fromObject(tmp.getObject());
823                if (dic)
824                        {
825                        int m=cls->findId("newFromDictionary");
826                        if (m>=0)
827                                {
828                                cls->call(m,&tmp,this);
829                                tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject());
830                                return ret;
831                                }
832                        }
833                if (tmp.getType()==TString)
834                        {
835                        int m=cls->findId("newFromString");
836                        if (m>=0)
837                                {
838                                cls->call(m,&tmp,this);
839                                tlsGetRef(ExtObject::serialization).replace(tmp.getObject(),getObject());
840                                return ret;
841                                }
842                        }
843                tlsGetRef(ExtObject::serialization).remove(tmp.getObject());
844                setEmpty();
845                }
846        setEmpty();
847        FMprintf("ExtValue","deserialize",FMLV_WARN,"object of class \"%s\" could not be deserialized",(const char*)clsname);
848        return ret;
849        }
850setEmpty();
851return NULL;
852}
853
854const char* ExtValue::deserialize(const char* in)
855{
856tlsGetRef(ExtObject::serialization).begin();
857const char* ret=deserialize_inner(in);
858tlsGetRef(ExtObject::serialization).end();
859return ret;
860}
861
862ExtObject ExtValue::getObject() const
863{
864if (type==TObj) return odata();
865return ExtObject();
866}
867
868ExtValue ExtValue::getExtType()
869{
870if (getType()!=TObj) return ExtValue((long)getType());
871ExtObject& o=odata();
872return ExtValue(SString(o.isEmpty()?"":o.interfaceName()));
873}
874
875SString SString::valueOf(const ExtValue& v)
876{
877return v.getString();
878}
879SString SString::valueOf(const ExtObject& v)
880{
881return v.toString();
882}
Note: See TracBrowser for help on using the repository browser.