Mercurial > projects > dmdscript-tango
view dmdscript_tango/value.d @ 3:8363a4bf6a8f
rename package: dmdscript to dmdscript_tango
author | saaadel |
---|---|
date | Sun, 24 Jan 2010 18:33:05 +0200 |
parents | 55c2951c07be |
children |
line wrap: on
line source
/* Digital Mars DMDScript source code. * Copyright (c) 2000-2002 by Chromium Communications * D version Copyright (c) 2004-2005 by Digital Mars * All Rights Reserved * written by Walter Bright * www.digitalmars.com * Use at your own risk. There is no warranty, express or implied. * License for redistribution is by the GNU General Public License in gpl.txt. * * A binary, non-exclusive license for commercial use can be * purchased from www.digitalmars.com/dscript/buy.html. * * DMDScript is implemented in the D Programming Language, * www.digitalmars.com/d/ * * For a C++ implementation of DMDScript, including COM support, * see www.digitalmars.com/dscript/cppscript.html. */ module dmdscript_tango.value; import std.math; import std.date; import std.string; import std.stdio; import std.c.string; import dmdscript_tango.script; import dmdscript_tango.dobject; import dmdscript_tango.iterator; import dmdscript_tango.identifier; import dmdscript_tango.textgen.errmsgs; import dmdscript_tango.text; import dmdscript_tango.program; import dmdscript_tango.dstring; import dmdscript_tango.dnumber; import dmdscript_tango.dboolean; // Porting issues: // A lot of scaling is done on arrays of Value's. Therefore, adjusting // it to come out to a size of 16 bytes makes the scaling an efficient // operation. In fact, in some cases (opcodes.c) we prescale the addressing // by 16 bytes at compile time instead of runtime. // So, Value must be looked at in any port to verify that: // 1) the size comes out as 16 bytes, padding as necessary // 2) Value::copy() copies the used data bytes, NOT the padding. // It's faster to not copy the padding, and the // padding can contain garbage stack pointers which can // prevent memory from being garbage collected. version (DigitalMars) version (D_InlineAsm) version = UseAsm; enum { V_NONE = 0, V_UNDEFINED = 1, V_NULL = 2, V_BOOLEAN = 3, V_NUMBER = 4, V_STRING = 5, V_OBJECT = 6, V_ITER = 7, } struct Value { ubyte vtype = V_UNDEFINED; uint hash; // cache 'hash' value union { d_boolean dbool; // can be true or false d_number number; tchar[] string; Dobject object; d_int32 int32; d_uint32 uint32; d_uint16 uint16; Iterator* iter; // V_ITER } void putVundefined() { vtype = V_UNDEFINED; hash = 0; string = null; } void putVnull() { vtype = V_NULL; } void putVboolean(d_boolean b) in { assert(b == 1 || b == 0); } body { vtype = V_BOOLEAN; dbool = b; } void putVnumber(d_number n) { vtype = V_NUMBER; number = n; } void putVtime(d_time n) { vtype = V_NUMBER; number = (n == d_time_nan) ? d_number.nan : n; } void putVstring(d_string s) { vtype = V_STRING; hash = 0; string = s; } void putVstring(d_string s, uint hash) { vtype = V_STRING; this.hash = hash; this.string = s; } void putVobject(Dobject o) { vtype = V_OBJECT; object = o; } void putViterator(Iterator* i) { vtype = V_ITER; iter = i; } invariant { /+ switch (vtype) { case V_UNDEFINED: case V_NULL: break; case V_BOOLEAN: assert(dbool == 1 || dbool == 0); break; case V_NUMBER: case V_STRING: case V_OBJECT: case V_ITER: break; case V_NONE: break; default: writefln("vtype = %d", vtype); assert(0); break; } +/ } static void copy(Value* to, Value* from) /+ in { } out { assert(memcmp(to, from, Value.sizeof) == 0); } body +/ { version (all /*UseAsm*/) { asm { naked ; push ESI ; mov ECX,[EAX] ; mov ESI,8[ESP] ; mov [ESI],ECX ; mov EDX,4[EAX] ; mov ECX,8[EAX] ; mov EAX,12[EAX] ; mov 4[ESI],EDX ; mov 8[ESI],ECX ; mov 12[ESI],EAX ; pop ESI ; ret 4 ; } } else { *to = *from; //(cast(uint *)to)[0] = (cast(uint *)from)[0]; //(cast(uint *)to)[1] = (cast(uint *)from)[1]; //(cast(uint *)to)[2] = (cast(uint *)from)[2]; //(cast(uint *)to)[3] = (cast(uint *)from)[3]; } } void* toPrimitive(Value* v, tchar[] PreferredType) { if (vtype == V_OBJECT) { /* ECMA 9.1 Return a default value for the Object. The default value of an object is retrieved by calling the internal [[DefaultValue]] method of the object, passing the optional hint PreferredType. The behavior of the [[DefaultValue]] method is defined by this specification for all native ECMAScript objects (see section 8.6.2.6). If the return value is of type Object or Reference, a runtime error is generated. */ void* a; assert(object); a = object.DefaultValue(v, PreferredType); if (a) return a; if (!v.isPrimitive()) { ErrInfo errinfo; v.putVundefined(); return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_OBJECT_CANNOT_BE_PRIMITIVE]); } } else { copy(v, this); } return null; } d_boolean toBoolean() { switch (vtype) { case V_UNDEFINED: case V_NULL: return false; case V_BOOLEAN: return dbool; case V_NUMBER: return !(number == 0.0 || isnan(number)); case V_STRING: return string.length ? true : false; case V_OBJECT: return true; default: assert(0); } assert(0); } d_number toNumber() { switch (vtype) { case V_UNDEFINED: return d_number.nan; case V_NULL: return 0; case V_BOOLEAN: return dbool ? 1 : 0; case V_NUMBER: return number; case V_STRING: { d_number n; size_t len; size_t endidx; len = string.length; n = StringNumericLiteral(string, endidx, 0); // Consume trailing whitespace //writefln("n = %s, string = '%s', endidx = %s, length = %s", n, string, endidx, string.length); foreach (dchar c; string[endidx .. length]) { if (!isStrWhiteSpaceChar(c)) { n = d_number.nan; break; } } return n; } case V_OBJECT: { Value val; Value* v; //writefln("Vobject.toNumber()"); v = &val; toPrimitive(v, TypeNumber); if (v.isPrimitive()) return v.toNumber(); else return d_number.nan; } default: assert(0); } assert(0); } d_time toDtime() { return cast(d_time)toNumber(); } d_number toInteger() { switch (vtype) { case V_UNDEFINED: return d_number.nan; case V_NULL: return 0; case V_BOOLEAN: return dbool ? 1 : 0; default: { d_number number; number = toNumber(); if (isnan(number)) number = 0; else if (number == 0 || std.math.isinf(number)) { } else if (number > 0) number = std.math.floor(number); else number = -std.math.floor(-number); return number; } } assert(0); } d_int32 toInt32() { switch (vtype) { case V_UNDEFINED: case V_NULL: return 0; case V_BOOLEAN: return dbool ? 1 : 0; default: { d_int32 int32; d_number number; long ll; number = toNumber(); if (isnan(number)) int32 = 0; else if (number == 0 || std.math.isinf(number)) int32 = 0; else { if (number > 0) number = std.math.floor(number); else number = -std.math.floor(-number); ll = cast(long) number; int32 = cast(int) ll; } return int32; } } assert(0); } d_uint32 toUint32() { switch (vtype) { case V_UNDEFINED: case V_NULL: return 0; case V_BOOLEAN: return dbool ? 1 : 0; default: { d_uint32 uint32; d_number number; long ll; number = toNumber(); if (isnan(number)) uint32 = 0; else if (number == 0 || std.math.isinf(number)) uint32 = 0; else { if (number > 0) number = std.math.floor(number); else number = -std.math.floor(-number); ll = cast(long) number; uint32 = cast(uint) ll; } return uint32; } } assert(0); } d_uint16 toUint16() { switch (vtype) { case V_UNDEFINED: case V_NULL: return 0; case V_BOOLEAN: return cast(d_uint16) (dbool ? 1 : 0); default: { d_uint16 uint16; d_number number; number = toNumber(); if (isnan(number)) uint16 = 0; else if (number == 0 || std.math.isinf(number)) uint16 = 0; else { if (number > 0) number = std.math.floor(number); else number = -std.math.floor(-number); uint16 = cast(ushort)number; } return uint16; } } assert(0); } d_string toString() { switch (vtype) { case V_UNDEFINED: return TEXT_undefined; case V_NULL: return TEXT_null; case V_BOOLEAN: return dbool ? TEXT_true : TEXT_false; case V_NUMBER: { d_string string; static d_string* strings[10] = [ &TEXT_0,&TEXT_1,&TEXT_2,&TEXT_3,&TEXT_4, &TEXT_5,&TEXT_6,&TEXT_7,&TEXT_8,&TEXT_9 ]; //writefln("Vnumber.toString(%g)", number); if (isnan(number)) string = TEXT_NaN; else if (number >= 0 && number <= 9 && number == cast(int) number) string = *strings[cast(int) number]; else if (std.math.isinf(number)) { if (number < 0) string = TEXT_negInfinity; else string = TEXT_Infinity; } else { tchar[100] buffer; // should shrink this to max size, // but doesn't really matter tchar* p; // ECMA 262 requires %.21g (21 digits) of precision. But, the // C runtime library doesn't handle that. Until the C runtime // library is upgraded to ANSI C 99 conformance, use // 16 digits, which is all the GCC library will round correctly. std.string.sformat(buffer, "%.16g\0", number); //std.c.stdio.sprintf(buffer.ptr, "%.16g", number); // Trim leading spaces // Trim leading spaces for (p = buffer.ptr; *p == ' '; p++) { } { // Trim any 0's following exponent 'e' tchar* q; tchar* t; for (q = p; *q; q++) { if (*q == 'e') { q++; if (*q == '+' || *q == '-') q++; t = q; while (*q == '0') q++; if (t != q) { for (;;) { *t = *q; if (*t == 0) break; t++; q++; } } break; } } } string = p[0 .. std.c.string.strlen(p)].dup; } //writefln("string = '%s'", string); return string; } case V_STRING: return string; case V_OBJECT: { Value val; Value* v = &val; void* a; //writef("Vobject.toString()\n"); a = toPrimitive(v, TypeString); //assert(!a); if (v.isPrimitive()) return v.toString(); else return v.toObject().classname; } default: assert(0); } assert(0); } d_string toLocaleString() { return toString(); } d_string toString(int radix) { if (vtype == V_NUMBER) { assert(2 <= radix && radix <= 36); return std.string.toString(cast(long)number, cast(uint)radix); } else { return toString(); } } d_string toSource() { switch (vtype) { case V_STRING: { d_string s; s = "\"" ~ string ~ "\""; return s; } case V_OBJECT: { Value* v; //writefln("Vobject.toSource()"); v = Get(TEXT_toSource); if (!v) v = &vundefined; if (v.isPrimitive()) return v.toSource(); else // it's an Object { void* a; CallContext *cc; Dobject o; Value* ret; Value val; o = v.object; cc = Program.getProgram().callcontext; ret = &val; a = o.Call(cc, this.object, ret, null); if (a) // if exception was thrown { /*return a*/; writef("Vobject.toSource() failed with %x\n", a); } else if (ret.isPrimitive()) return ret.toString(); } return TEXT_undefined; } default: return toString(); } assert(0); } Dobject toObject() { switch (vtype) { case V_UNDEFINED: //RuntimeErrorx("cannot convert undefined to Object"); return null; case V_NULL: //RuntimeErrorx("cannot convert null to Object"); return null; case V_BOOLEAN: return new Dboolean(dbool); case V_NUMBER: return new Dnumber(number); case V_STRING: return new Dstring(string); case V_OBJECT: return object; default: assert(0); } assert(0); } int opEquals(Value* v) { return (opCmp(v) == 0); } /********************************* * Use this instead of std.string.cmp() because * we don't care about lexicographic ordering. * This is faster. */ static int stringcmp(d_string s1, d_string s2) { int c = s1.length - s2.length; if (c == 0) { if (s1.ptr == s2.ptr) return 0; c = memcmp(s1.ptr, s2.ptr, s1.length); } return c; } int opCmp(Value* v) { switch (vtype) { case V_UNDEFINED: if (vtype == v.vtype) return 0; break; case V_NULL: if (vtype == v.vtype) return 0; break; case V_BOOLEAN: if (vtype == v.vtype) return v.dbool - dbool; break; case V_NUMBER: if (v.vtype == V_NUMBER) { if (number == v.number) return 0; if (isnan(number) && isnan(v.number)) return 0; if (number > v.number) return 1; } else if (v.vtype == V_STRING) { return stringcmp(toString(), v.string); } break; case V_STRING: if (v.vtype == V_STRING) { //writefln("'%s'.compareTo('%s')", string, v.string); int len = string.length - v.string.length; if (len == 0) { if (string.ptr == v.string.ptr) return 0; len = memcmp(string.ptr, v.string.ptr, string.length); } return len; } else if (v.vtype == V_NUMBER) { //writefln("'%s'.compareTo(%g)\n", string, v.number); return stringcmp(string, v.toString()); } break; case V_OBJECT: if (v.object == object) return 0; break; default: assert(0); } return -1; } void copyTo(Value* v) { // Copy everything, including vptr copy(this, v); } tchar[] getType() { tchar[] s; switch (vtype) { case V_UNDEFINED: s = TypeUndefined; break; case V_NULL: s = TypeNull; break; case V_BOOLEAN: s = TypeBoolean; break; case V_NUMBER: s = TypeNumber; break; case V_STRING: s = TypeString; break; case V_OBJECT: s = TypeObject; break; case V_ITER: s = TypeIterator; break; default: writefln("vtype = %d", vtype); assert(0); } return s; } d_string getTypeof() { tchar[] s; switch (vtype) { case V_UNDEFINED: s = TEXT_undefined; break; case V_NULL: s = TEXT_object; break; case V_BOOLEAN: s = TEXT_boolean; break; case V_NUMBER: s = TEXT_number; break; case V_STRING: s = TEXT_string; break; case V_OBJECT: s = object.getTypeof(); break; default: writefln("vtype = %d", vtype); assert(0); } return s; } int isUndefined() { return vtype == V_UNDEFINED; } int isNull() { return vtype == V_NULL; } int isBoolean() { return vtype == V_BOOLEAN; } int isNumber() { return vtype == V_NUMBER; } int isString() { return vtype == V_STRING; } int isObject() { return vtype == V_OBJECT; } int isIterator() { return vtype == V_ITER; } int isUndefinedOrNull() { return vtype == V_UNDEFINED || vtype == V_NULL; } int isPrimitive() { return vtype != V_OBJECT; } int isArrayIndex(out d_uint32 index) { switch (vtype) { case V_NUMBER: index = toUint32(); return true; case V_STRING: return StringToIndex(string, index); default: index = 0; return false; } assert(0); } static uint calcHash(uint u) { return u ^ 0x55555555; } static uint calcHash(double d) { return calcHash(cast(uint) d); } static uint calcHash(d_string s) { uint hash; /* If it looks like an array index, hash it to the * same value as if it was an array index. * This means that "1234" hashes to the same value as 1234. */ hash = 0; foreach (tchar c; s) { switch (c) { case '0': hash *= 10; break; case '1': hash = hash * 10 + 1; break; case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': hash = hash * 10 + (c - '0'); break; default: { uint len = s.length; ubyte *str = cast(ubyte*)s.ptr; hash = 0; while (1) { switch (len) { case 0: break; case 1: hash *= 9; hash += *cast(ubyte *)str; break; case 2: hash *= 9; hash += *cast(ushort *)str; break; case 3: hash *= 9; hash += (*cast(ushort *)str << 8) + (cast(ubyte *)str)[2]; break; default: hash *= 9; hash += *cast(uint *)str; str += 4; len -= 4; continue; } break; } break; } // return s.hash; } } return calcHash(hash); } uint toHash() { uint h; switch (vtype) { case V_UNDEFINED: case V_NULL: h = 0; break; case V_BOOLEAN: h = dbool ? 1 : 0; break; case V_NUMBER: h = calcHash(number); break; case V_STRING: // Since strings are immutable, if we've already // computed the hash, use previous value if (!hash) hash = calcHash(string); h = hash; break; case V_OBJECT: /* Uses the address of the object as the hash. * Since the object never moves, it will work * as its hash. * BUG: shouldn't do this. */ h = cast(uint)cast(void*) object; break; default: assert(0); } //writefln("\tValue.toHash() = %x", h); return h; } Value* Put(d_string PropertyName, Value* value) { if (vtype == V_OBJECT) return object.Put(PropertyName, value, 0); else { ErrInfo errinfo; return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_CANNOT_PUT_TO_PRIMITIVE], PropertyName, value.toString(), getType()); } } Value* Put(d_uint32 index, Value* vindex, Value* value) { if (vtype == V_OBJECT) return object.Put(index, vindex, value, 0); else { ErrInfo errinfo; return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_CANNOT_PUT_INDEX_TO_PRIMITIVE], index, value.toString(), getType()); } } Value* Get(d_string PropertyName) { if (vtype == V_OBJECT) return object.Get(PropertyName); else { // Should we generate the error, or just return undefined? tchar[] msg; msg = std.string.format(errmsgtbl[ERR_CANNOT_GET_FROM_PRIMITIVE], PropertyName, getType(), toString()); throw new ScriptException(msg); //return &vundefined; } } Value* Get(d_uint32 index) { if (vtype == V_OBJECT) return object.Get(index); else { // Should we generate the error, or just return undefined? tchar[] msg; msg = std.string.format(errmsgtbl[ERR_CANNOT_GET_INDEX_FROM_PRIMITIVE], index, getType(), toString()); throw new ScriptException(msg); //return &vundefined; } } Value* Get(Identifier *id) { if (vtype == V_OBJECT) return object.Get(id); else { // Should we generate the error, or just return undefined? tchar[] msg; msg = std.string.format(errmsgtbl[ERR_CANNOT_GET_FROM_PRIMITIVE], id.toString(), getType(), toString()); throw new ScriptException(msg); //return &vundefined; } } /+ Value* Get(d_string PropertyName, uint hash) { if (vtype == V_OBJECT) return object.Get(PropertyName, hash); else { // Should we generate the error, or just return undefined? tchar[] msg; msg = std.string.format(errmsgtbl[ERR_CANNOT_GET_FROM_PRIMITIVE], PropertyName, getType(), toString()); throw new ScriptException(msg); //return &vundefined; } } +/ void* Construct(CallContext *cc, Value *ret, Value[] arglist) { if (vtype == V_OBJECT) return object.Construct(cc, ret, arglist); else { ErrInfo errinfo; ret.putVundefined(); return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_PRIMITIVE_NO_CONSTRUCT], getType()); } } void* Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist) { if (vtype == V_OBJECT) { void* a; a = object.Call(cc, othis, ret, arglist); //if (a) writef("Vobject.Call() returned %x\n", a); return a; } else { ErrInfo errinfo; //PRINTF("Call method not implemented for primitive %p (%s)\n", this, d_string_ptr(toString())); ret.putVundefined(); return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_PRIMITIVE_NO_CALL], getType()); } } Value* putIterator(Value* v) { if (vtype == V_OBJECT) return object.putIterator(v); else { ErrInfo errinfo; v.putVundefined(); return Dobject.RuntimeError(&errinfo, errmsgtbl[ERR_FOR_IN_MUST_BE_OBJECT]); } } void getErrInfo(ErrInfo *perrinfo, int linnum) { if (vtype == V_OBJECT) object.getErrInfo(perrinfo, linnum); else { ErrInfo errinfo; if (linnum && errinfo.linnum == 0) errinfo.linnum = linnum; errinfo.message = "Unhandled exception: " ~ toString(); if (perrinfo) *perrinfo = errinfo; } } void dump() { uint *v = cast(uint *)this; writef("v[%x] = %8x, %8x, %8x, %8x\n", cast(uint)v, v[0], v[1], v[2], v[3]); } } static assert(Value.sizeof == 16); Value vundefined = { V_UNDEFINED }; Value vnull = { V_NULL }; tchar[] TypeUndefined = "Undefined"; tchar[] TypeNull = "Null"; tchar[] TypeBoolean = "Boolean"; tchar[] TypeNumber = "Number"; tchar[] TypeString = "String"; tchar[] TypeObject = "Object"; tchar[] TypeIterator = "Iterator";