diff dmdscript_tango/dstring.d @ 0:55c2951c07be

initial, files origin, premoved tree
author saaadel
date Sun, 24 Jan 2010 12:34:47 +0200
parents
children 8363a4bf6a8f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmdscript_tango/dstring.d	Sun Jan 24 12:34:47 2010 +0200
@@ -0,0 +1,1217 @@
+
+/* Digital Mars DMDScript source code.
+ * Copyright (c) 2000-2002 by Chromium Communications
+ * D version Copyright (c) 2004-2006 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.dstring;
+
+import std.regexp;
+import std.utf;
+import std.c.stdlib;
+import std.c.string;
+
+import dmdscript.script;
+import dmdscript.dobject;
+import dmdscript.dregexp;
+import dmdscript.darray;
+import dmdscript.value;
+import dmdscript.threadcontext;
+import dmdscript.dfunction;
+import dmdscript.text;
+import dmdscript.property;
+import dmdscript.errmsgs;
+import dmdscript.dnative;
+
+//alias script.tchar tchar;
+
+/* ===================== Dstring_fromCharCode ==================== */
+
+void* Dstring_fromCharCode(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.3.2
+    d_string s;
+
+    for (size_t i = 0; i < arglist.length; i++)
+    {	Value* v;
+	uint u;
+
+	v = &arglist[i];
+	u = v.toUint16();
+	//writefln("string.fromCharCode(%x)", u);
+	if (!std.utf.isValidDchar(u))
+	{
+	    ErrInfo errinfo;
+
+	    ret.putVundefined();
+	    return pthis.RuntimeError(&errinfo,
+		    errmsgtbl[ERR_NOT_VALID_UTF],
+		    "String", "fromCharCode()",
+		    u);
+	}
+	std.utf.encode(s, u);
+	//writefln("s[0] = %x, s = '%s'", s[0], s);
+    }
+    ret.putVstring(s);
+    return null;
+}
+
+/* ===================== Dstring_constructor ==================== */
+
+class Dstring_constructor : Dfunction
+{
+    this(ThreadContext *tc)
+    {
+	super(1, tc.Dfunction_prototype);
+	name = "String";
+
+	static NativeFunctionData nfd[] =
+	[
+	    {	&TEXT_fromCharCode, &Dstring_fromCharCode, 1 },
+	];
+
+	DnativeFunction.init(this, nfd, 0);
+    }
+
+    void *Construct(CallContext *cc, Value *ret, Value[] arglist)
+    {
+	// ECMA 15.5.2
+	d_string s;
+	Dobject o;
+
+	s = (arglist.length) ? arglist[0].toString() : TEXT_;
+	o = new Dstring(s);
+	ret.putVobject(o);
+	return null;
+    }
+
+    void *Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist)
+    {
+	// ECMA 15.5.1
+	d_string s;
+
+	s = (arglist.length) ? arglist[0].toString() : TEXT_;
+	ret.putVstring(s);
+	return null;
+    }
+}
+
+
+/* ===================== Dstring_prototype_toString =============== */
+
+void* Dstring_prototype_toString(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    //writef("Dstring.prototype.toString()\n");
+    // othis must be a String
+    if (!othis.isClass(TEXT_String))
+    {
+	ErrInfo errinfo;
+
+	ret.putVundefined();
+	return pthis.RuntimeError(&errinfo,
+		errmsgtbl[ERR_FUNCTION_WANTS_STRING],
+		TEXT_toString,
+		othis.classname);
+    }
+    else
+    {	Value *v;
+
+	v = &(cast(Dstring)othis).value;
+	Value.copy(ret, v);
+    }
+    return null;
+}
+
+/* ===================== Dstring_prototype_valueOf =============== */
+
+void* Dstring_prototype_valueOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // Does same thing as String.prototype.toString()
+
+    //writef("string.prototype.valueOf()\n");
+    // othis must be a String
+    if (!othis.isClass(TEXT_String))
+    {
+	ErrInfo errinfo;
+
+	ret.putVundefined();
+	return pthis.RuntimeError(&errinfo,
+		errmsgtbl[ERR_FUNCTION_WANTS_STRING],
+		TEXT_valueOf,
+		othis.classname);
+    }
+    else
+    {	Value *v;
+
+	v = &(cast(Dstring)othis).value;
+	Value.copy(ret, v);
+    }
+    return null;
+}
+
+/* ===================== Dstring_prototype_charAt =============== */
+
+void* Dstring_prototype_charAt(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.4
+
+    Value *v;
+    int pos;		// ECMA says pos should be a d_number,
+			// but int should behave the same
+    d_string s;
+    d_string result;
+
+    v = &othis.value;
+    s = v.toString();
+    v = arglist.length ? &arglist[0] : &vundefined;
+    pos = cast(int) v.toInteger();
+
+    result = TEXT_;
+
+    if (pos >= 0)
+    {	size_t idx;
+
+	while (1)
+	{
+	    if (idx == s.length)
+		break;
+	    if (pos == 0)
+	    {
+		result = s[idx .. idx + std.utf.stride(s, idx)];
+		break;
+	    }
+	    idx += std.utf.stride(s, idx);
+	    pos--;
+	}
+    }
+
+    ret.putVstring(result);
+    return null;
+}
+
+/* ===================== Dstring_prototype_charCodeAt ============= */
+
+void* Dstring_prototype_charCodeAt(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.5
+
+    Value *v;
+    int pos;		// ECMA says pos should be a d_number,
+			// but int should behave the same
+    d_string s;
+    uint len;
+    d_number result;
+
+    v = &othis.value;
+    s = v.toString();
+    v = arglist.length ? &arglist[0] : &vundefined;
+    pos = cast(int) v.toInteger();
+
+    result = d_number.nan;
+
+    if (pos >= 0)
+    {	size_t idx;
+
+	while (1)
+	{
+	    assert(idx <= s.length);
+	    if (idx == s.length)
+		break;
+	    if (pos == 0)
+	    {
+		result = std.utf.decode(s, idx);
+		break;
+	    }
+	    idx += std.utf.stride(s, idx);
+	    pos--;
+	}
+    }
+
+    ret.putVnumber(result);
+    return null;
+}
+
+/* ===================== Dstring_prototype_concat ============= */
+
+void* Dstring_prototype_concat(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.6
+    d_string s;
+
+    //writefln("Dstring.prototype.concat()");
+
+    s = othis.value.toString();
+    for (size_t a = 0; a < arglist.length; a++)
+	s ~= arglist[a].toString();
+
+    ret.putVstring(s);
+    return null;
+}
+
+/* ===================== Dstring_prototype_indexOf ============= */
+
+void* Dstring_prototype_indexOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.6
+    // String.prototype.indexOf(searchString, position)
+
+    Value* v1;
+    Value* v2;
+    int pos;		// ECMA says pos should be a d_number,
+			// but I can't find a reason.
+    d_string s;
+    size_t sUCSdim;
+
+    d_string searchString;
+    int k;
+
+    Value xx;
+    xx.putVobject(othis);
+    s = xx.toString();
+    sUCSdim = std.utf.toUCSindex(s, s.length);
+
+    v1 = arglist.length ? &arglist[0] : &vundefined;
+    v2 = (arglist.length >= 2) ? &arglist[1] : &vundefined;
+
+    searchString = v1.toString();
+    pos = cast(int) v2.toInteger();
+
+    if (pos < 0)
+	pos = 0;
+    else if (pos > sUCSdim)
+	pos = sUCSdim;
+
+    if (searchString.length == 0)
+	k = pos;
+    else
+    {	pos = std.utf.toUTFindex(s, pos);
+	k = std.string.find(s[pos .. length], searchString);
+	if (k != -1)
+	    k = std.utf.toUCSindex(s, pos + k);
+    }
+
+    ret.putVnumber(k);
+    return null;
+}
+
+/* ===================== Dstring_prototype_lastIndexOf ============= */
+
+void* Dstring_prototype_lastIndexOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.8
+    // String.prototype.lastIndexOf(searchString, position)
+
+    Value *v1;
+    int pos;		// ECMA says pos should be a d_number,
+			// but I can't find a reason.
+    d_string s;
+    size_t sUCSdim;
+    d_string searchString;
+    int k;
+
+version (all)
+{
+    {
+    // This is the 'transferable' version
+    Value *v;
+    void *a;
+    v = othis.Get(TEXT_toString);
+    a = v.Call(cc, othis, ret, null);
+    if (a)				// if exception was thrown
+	return a;
+    s = ret.toString();
+    }
+}
+else
+{
+    // the 'builtin' version
+    s = othis.value.toString();
+}
+    sUCSdim = std.utf.toUCSindex(s, s.length);
+
+    v1 = arglist.length ? &arglist[0] : &vundefined;
+    searchString = v1.toString();
+    if (arglist.length >= 2)
+    {	d_number n;
+	Value *v = &arglist[1];
+
+	n = v.toNumber();
+	if (std.math.isnan(n) || n > sUCSdim)
+	    pos = sUCSdim;
+	else if (n < 0)
+	    pos = 0;
+	else
+	    pos = cast(int) n;
+    }
+    else
+	pos = sUCSdim;
+
+    //writef("len = %d, p = '%ls'\n", len, p);
+    //writef("pos = %d, sslen = %d, ssptr = '%ls'\n", pos, sslen, ssptr);
+    //writefln("s = '%s', pos = %s, searchString = '%s'", s, pos, searchString);
+
+    if (searchString.length == 0)
+	k = pos;
+    else
+    {
+	pos = std.utf.toUTFindex(s, pos);
+	pos += searchString.length;
+	if (pos > s.length)
+	    pos = s.length;
+	k = std.string.rfind(s[0 .. pos], searchString);
+	//writefln("s = '%s', pos = %s, searchString = '%s', k = %d", s, pos, searchString, k);
+	if (k != -1)
+	    k = std.utf.toUCSindex(s, k);
+    }
+    ret.putVnumber(k);
+    return null;
+}
+
+/* ===================== Dstring_prototype_localeCompare ============= */
+
+void* Dstring_prototype_localeCompare(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.9
+    d_string s1;
+    d_string s2;
+    d_number n;
+    Value *v;
+
+    v = &othis.value;
+    s1 = v.toString();
+    s2 = arglist.length ? arglist[0].toString() : vundefined.toString();
+    n = localeCompare(cc, s1, s2);
+    ret.putVnumber(n);
+    return null;
+}
+
+/* ===================== Dstring_prototype_match ============= */
+
+void* Dstring_prototype_match(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.10
+    Dregexp r;
+    Dobject o;
+
+    if (arglist.length && !arglist[0].isPrimitive() &&
+	(o = arglist[0].toObject()).isDregexp())
+    {
+	;
+    }
+    else
+    {
+	Value regret;
+
+	regret.putVobject(null);
+	Dregexp.getConstructor().Construct(cc, &regret, arglist);
+	o = regret.object;
+    }
+
+    r = cast(Dregexp)o;
+    if (r.global.dbool)
+    {
+	Darray a = new Darray;
+	d_int32 n;
+	d_int32 i;
+	d_int32 lasti;
+
+	i = 0;
+	lasti = 0;
+	for (n = 0; ; n++)
+	{
+	    r.lastIndex.putVnumber(i);
+	    Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_STRING);
+	    if (!ret.string)		// if match failed
+	    {
+		r.lastIndex.putVnumber(i);
+		break;
+	    }
+	    lasti = i;
+	    i = cast(d_int32) r.lastIndex.toInt32();
+	    if (i == lasti)		// if no source was consumed
+		i++;			// consume a character
+
+	    a.Put(n, ret, 0);		// a[n] = ret;
+	}
+	ret.putVobject(a);
+    }
+    else
+    {
+	Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_ARRAY);
+    }
+    return null;
+}
+
+/* ===================== Dstring_prototype_replace ============= */
+
+void* Dstring_prototype_replace(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.11
+    // String.prototype.replace(searchValue, replaceValue)
+
+    d_string string;
+    d_string searchString;
+    d_string newstring;
+    Value *searchValue;
+    Value *replaceValue;
+    Dregexp r;
+    RegExp re;
+    tchar[] replacement;
+    d_string result;
+    int m;
+    int i;
+    int lasti;
+    std.regexp.regmatch_t[1] pmatch;
+    Dfunction f;
+    Value* v;
+
+    v = &othis.value;
+    string = v.toString();
+    searchValue  = (arglist.length >= 1) ? &arglist[0] : &vundefined;
+    replaceValue = (arglist.length >= 2) ? &arglist[1] : &vundefined;
+    r = Dregexp.isRegExp(searchValue);
+    f = Dfunction.isFunction(replaceValue);
+    if (r)
+    {	int offset = 0;
+
+	re = r.re;
+	i = 0;
+	result = string;
+
+	r.lastIndex.putVnumber(0);
+	for (;;)
+	{
+	    Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_STRING);
+	    if (!ret.string)		// if match failed
+		break;
+
+	    m = re.re_nsub;
+	    if (f)
+	    {
+		Value* alist;
+
+		alist = cast(Value *)alloca((m + 3) * Value.sizeof);
+		assert(alist);
+		alist[0].putVstring(ret.string);
+		for (i = 0; i < m; i++)
+		{
+		    alist[1 + i].putVstring(
+			string[re.pmatch[1 + i].rm_so .. re.pmatch[1 + i].rm_eo]);
+		}
+		alist[m + 1].putVnumber(re.pmatch[0].rm_so);
+		alist[m + 2].putVstring(string);
+		f.Call(cc, f, ret, alist[0 .. m + 3]);
+		replacement = ret.toString();
+	    }
+	    else
+	    {
+		newstring = replaceValue.toString();
+		replacement = re.replace(newstring);
+	    }
+	    int starti = re.pmatch[0].rm_so + offset;
+	    int endi   = re.pmatch[0].rm_eo + offset;
+	    result = string[0 .. starti] ~
+		     replacement ~
+		     string[endi .. length];
+
+	    if (re.attributes & RegExp.REA.global)
+	    {
+		offset += replacement.length - (endi - starti);
+
+		// If no source was consumed, consume a character
+		lasti = i;
+		i = cast(d_int32) r.lastIndex.toInt32();
+		if (i == lasti)
+		{   i++;
+		    r.lastIndex.putVnumber(i);
+		}
+	    }
+	    else
+		break;
+	}
+    }
+    else
+    {	int match;
+
+	searchString = searchValue.toString();
+	match = std.string.find(string, searchString);
+	if (match >= 0)
+	{
+	    pmatch[0].rm_so = match;
+	    pmatch[0].rm_eo = match + searchString.length;
+	    if (f)
+	    {
+		Value[3] alist;
+
+		alist[0].putVstring(searchString);
+		alist[1].putVnumber(pmatch[0].rm_so);
+		alist[2].putVstring(string);
+		f.Call(cc, f, ret, alist);
+		replacement = ret.toString();
+	    }
+	    else
+	    {
+		newstring = replaceValue.toString();
+		replacement = RegExp.replace3(newstring, string, pmatch);
+	    }
+	    result = string[0 .. match] ~
+		     replacement ~
+		     string[match + searchString.length .. length];
+	}
+	else
+	{
+	    result = string;
+	}
+    }
+
+    ret.putVstring(result);
+    return null;
+}
+
+/* ===================== Dstring_prototype_search ============= */
+
+void* Dstring_prototype_search(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.12
+    Dregexp r;
+    Dobject o;
+
+    //writef("String.prototype.search()\n");
+    if (arglist.length && !arglist[0].isPrimitive() &&
+	(o = arglist[0].toObject()).isDregexp())
+    {
+	;
+    }
+    else
+    {	Value regret;
+
+	regret.putVobject(null);
+	Dregexp.getConstructor().Construct(cc, &regret, arglist);
+	o = regret.object;
+    }
+
+    r = cast(Dregexp)o;
+    Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_INDEX);
+    return null;
+}
+
+/* ===================== Dstring_prototype_slice ============= */
+
+void* Dstring_prototype_slice(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.13
+    d_int32 start;
+    d_int32 end;
+    d_int32 sUCSdim;
+    d_string s;
+    d_string r;
+    Value *v;
+
+    v = &othis.value;
+    s = v.toString();
+    sUCSdim = std.utf.toUCSindex(s, s.length);
+    switch (arglist.length)
+    {
+	case 0:
+	    start = 0;
+	    end = sUCSdim;
+	    break;
+
+	case 1:
+	    start = arglist[0].toInt32();
+	    end = sUCSdim;
+	    break;
+
+	default:
+	    start = arglist[0].toInt32();
+	    end   = arglist[1].toInt32();
+	    break;
+    }
+
+    if (start < 0)
+    {
+	start += sUCSdim;
+	if (start < 0)
+	    start = 0;
+    }
+    else if (start >= sUCSdim)
+	start = sUCSdim;
+
+    if (end < 0)
+    {
+	end += sUCSdim;
+	if (end < 0)
+	    end = 0;
+    }
+    else if (end >= sUCSdim)
+	end = sUCSdim;
+
+    if (start > end)
+	end = start;
+
+    start = toUTFindex(s, start);
+    end   = toUTFindex(s, end);
+    r = s[start .. end];
+
+    ret.putVstring(r);
+    return null;
+}
+
+
+/* ===================== Dstring_prototype_split ============= */
+
+void* Dstring_prototype_split(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.14
+    // String.prototype.split(separator, limit)
+    d_uint32 lim;
+    d_uint32 p;
+    d_uint32 q;
+    d_uint32 e;
+    Value* separator = &vundefined;
+    Value* limit = &vundefined;
+    Dregexp R;
+    RegExp re;
+    d_string rs;
+    d_string T;
+    d_string S;
+    Darray A;
+    int str;
+
+    //writefln("Dstring_prototype_split()");
+    switch (arglist.length)
+    {
+	default:
+	    limit = &arglist[1];
+	case 1:
+	    separator = &arglist[0];
+	case 0:
+	    break;
+    }
+
+    Value *v;
+    v = &othis.value;
+    S = v.toString();
+    A = new Darray;
+    if (limit.isUndefined())
+	lim = ~0u;
+    else
+	lim = limit.toUint32();
+    p = 0;
+    R = Dregexp.isRegExp(separator);
+    if (R)	// regular expression
+    {	re = R.re;
+	assert(re);
+	rs = null;
+	str = 0;
+    }
+    else	// string
+    {	re = null;
+	rs = separator.toString();
+	str = 1;
+    }
+    if (lim == 0)
+	goto Lret;
+
+    // ECMA v3 15.5.4.14 is specific: "If separator is undefined, then the
+    // result array contains just one string, which is the this value
+    // (converted to a string)." However, neither Javascript nor Jscript
+    // do that, they regard an undefined as being the string "undefined".
+    // We match Javascript/Jscript behavior here, not ECMA.
+
+    // Uncomment for ECMA compatibility
+    //if (!separator.isUndefined())
+    {
+	//writefln("test1 S = '%s', rs = '%s'", S, rs);
+	if (S.length)
+	{
+	L10:
+	    for (q = p; q != S.length; q++)
+	    {
+		if (str)		// string
+		{
+		    if (q + rs.length <= S.length && !memcmp(S.ptr + q, rs.ptr, rs.length * tchar.sizeof))
+		    {
+			e = q + rs.length;
+			if (e != p)
+			{
+			    T = S[p .. q];
+			    A.Put(cast(uint) A.length.number, T, 0);
+			    if (A.length.number == lim)
+				goto Lret;
+			    p = e;
+			    goto L10;
+			}
+		    }
+		}
+		else		// regular expression
+		{
+		    if (re.test(S, q))
+		    {	q = re.pmatch[0].rm_so;
+			e = re.pmatch[0].rm_eo;
+			if (e != p)
+			{
+			    T = S[p .. q];
+			    //writefln("S = '%s', T = '%s', p = %d, q = %d, e = %d\n", S, T, p, q, e);
+			    A.Put(cast(uint) A.length.number, T, 0);
+			    if (A.length.number == lim)
+				goto Lret;
+			    p = e;
+			    for (uint i = 0; i < re.re_nsub; i++)
+			    {
+				int so = re.pmatch[1 + i].rm_so;
+				int eo = re.pmatch[1 + i].rm_eo;
+
+				//writefln("i = %d, nsub = %s, so = %s, eo = %s, S.length = %s", i, re.re_nsub, so, eo, S.length);
+				if (so != -1 && eo != -1)
+				    T = S[so .. eo];
+				else
+				    T = null;
+				A.Put(cast(uint) A.length.number, T, 0);
+				if (A.length.number == lim)
+				    goto Lret;
+			    }
+			    goto L10;
+			}
+		    }
+		}
+	    }
+	    T = S[p .. S.length];
+	    A.Put(cast(uint) A.length.number, T, 0);
+	    goto Lret;
+	}
+	if (str)		// string
+	{
+	    if (rs.length <= S.length && S[0 .. rs.length] == rs[])
+		goto Lret;
+	}
+	else		// regular expression
+	{
+	    if (re.test(S, 0))
+		goto Lret;
+	}
+    }
+
+    A.Put(0u, S, 0);
+Lret:
+    ret.putVobject(A);
+    return null;
+}
+
+
+/* ===================== Dstring_prototype_substr ============= */
+
+void *dstring_substring(d_string s, size_t sUCSdim, d_number start, d_number end, Value *ret)
+{
+    d_string sb;
+    d_int32 sb_len;
+
+    if (std.math.isnan(start))
+	start = 0;
+    else if (start > sUCSdim)
+	start = sUCSdim;
+    else if (start < 0)
+	start = 0;
+
+    if (std.math.isnan(end))
+	end = 0;
+    else if (end > sUCSdim)
+	end = sUCSdim;
+    else if (end < 0)
+	end = 0;
+
+    if (end < start)		// swap
+    {	d_number t;
+
+	t = start;
+	start = end;
+	end = t;
+    }
+
+    size_t st = std.utf.toUTFindex(s, cast(size_t)start);
+    size_t en = std.utf.toUTFindex(s, cast(size_t)end);
+    sb = s[st .. en];
+
+    ret.putVstring(sb);
+    return null;
+}
+
+void* Dstring_prototype_substr(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // Javascript: TDG pg. 689
+    // String.prototype.substr(start, length)
+    d_number start;
+    d_number length;
+    d_string s;
+
+    s = othis.value.toString();
+    size_t sUCSdim = std.utf.toUCSindex(s, s.length);
+    start = 0;
+    length = 0;
+    if (arglist.length >= 1)
+    {
+	start = arglist[0].toInteger();
+	if (start < 0)
+	    start = sUCSdim + start;
+	if (arglist.length >= 2)
+	{
+	    length = arglist[1].toInteger();
+	    if (std.math.isnan(length) || length < 0)
+		length = 0;
+	}
+	else
+	    length = sUCSdim - start;
+    }
+
+    return dstring_substring(s, sUCSdim, start, start + length, ret);
+}
+
+/* ===================== Dstring_prototype_substring ============= */
+
+void* Dstring_prototype_substring(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.9
+    // String.prototype.substring(start)
+    // String.prototype.substring(start, end)
+    d_number start;
+    d_number end;
+    d_string s;
+
+    //writefln("String.prototype.substring()");
+    s = othis.value.toString();
+    size_t sUCSdim = std.utf.toUCSindex(s, s.length);
+    start = 0;
+    end = sUCSdim;
+    if (arglist.length >= 1)
+    {
+	start = arglist[0].toInteger();
+	if (arglist.length >= 2)
+	    end = arglist[1].toInteger();
+	//writef("s = '%ls', start = %d, end = %d\n", s, start, end);
+    }
+
+    void* p = dstring_substring(s, sUCSdim, start, end, ret);
+    return p;
+}
+
+/* ===================== Dstring_prototype_toLowerCase ============= */
+
+enum CASE
+{
+    Lower,
+    Upper,
+    LocaleLower,
+    LocaleUpper
+};
+
+void *tocase(Dobject othis, Value *ret, CASE caseflag)
+{
+    d_string s;
+
+    s = othis.value.toString();
+    switch (caseflag)
+    {
+	case CASE.Lower:
+	    s = std.string.tolower(s);
+	    break;
+	case CASE.Upper:
+	    s = std.string.toupper(s);
+	    break;
+	case CASE.LocaleLower:
+	    s = std.string.tolower(s);
+	    break;
+	case CASE.LocaleUpper:
+	    s = std.string.toupper(s);
+	    break;
+	default:
+	    assert(0);
+    }
+
+    ret.putVstring(s);
+    return null;
+}
+
+void* Dstring_prototype_toLowerCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.11
+    // String.prototype.toLowerCase()
+
+    //writef("Dstring_prototype_toLowerCase()\n");
+    return tocase(othis, ret, CASE.Lower);
+}
+
+/* ===================== Dstring_prototype_toLocaleLowerCase ============= */
+
+void* Dstring_prototype_toLocaleLowerCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.17
+
+    //writef("Dstring_prototype_toLocaleLowerCase()\n");
+    return tocase(othis, ret, CASE.LocaleLower);
+}
+
+/* ===================== Dstring_prototype_toUpperCase ============= */
+
+void* Dstring_prototype_toUpperCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA 15.5.4.12
+    // String.prototype.toUpperCase()
+
+    return tocase(othis, ret, CASE.Upper);
+}
+
+/* ===================== Dstring_prototype_toLocaleUpperCase ============= */
+
+void* Dstring_prototype_toLocaleUpperCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // ECMA v3 15.5.4.18
+
+    return tocase(othis, ret, CASE.LocaleUpper);
+}
+
+/* ===================== Dstring_prototype_anchor ============= */
+
+void *dstring_anchor(Dobject othis, Value* ret, tchar[] tag, tchar[] name, Value[] arglist)
+{
+    // For example:
+    //	"foo".anchor("bar")
+    // produces:
+    //	<tag name="bar">foo</tag>
+
+    d_string foo = othis.value.toString();
+    Value* va = arglist.length ? &arglist[0] : &vundefined;
+    d_string bar = va.toString();
+
+    d_string s;
+
+    s = "<"	~
+	tag	~
+	" "	~
+	name	~
+	"=\""	~
+	bar	~
+	"\">"	~
+	foo	~
+	"</"	~
+	tag	~
+	">";
+
+    ret.putVstring(s);
+    return null;
+}
+
+
+void* Dstring_prototype_anchor(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // Non-standard extension
+    // String.prototype.anchor(anchor)
+    // For example:
+    //	"foo".anchor("bar")
+    // produces:
+    //	<A NAME="bar">foo</A>
+
+    return dstring_anchor(othis, ret, "A", "NAME", arglist);
+}
+
+void* Dstring_prototype_fontcolor(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_anchor(othis, ret, "FONT", "COLOR", arglist);
+}
+
+void* Dstring_prototype_fontsize(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_anchor(othis, ret, "FONT", "SIZE", arglist);
+}
+
+void* Dstring_prototype_link(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_anchor(othis, ret, "A", "HREF", arglist);
+}
+
+
+/* ===================== Dstring_prototype bracketing ============= */
+
+/***************************
+ * Produce <tag>othis</tag>
+ */
+
+void *dstring_bracket(Dobject othis, Value* ret, char[] tag)
+{
+    d_string foo = othis.value.toString();
+    d_string s;
+
+    s = "<"	~
+	tag	~
+	">"	~
+	foo	~
+	"</"	~
+	tag	~
+	">";
+
+    ret.putVstring(s);
+    return null;
+}
+
+void* Dstring_prototype_big(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    // Non-standard extension
+    // String.prototype.big()
+    // For example:
+    //	"foo".big()
+    // produces:
+    //	<BIG>foo</BIG>
+
+    return dstring_bracket(othis, ret, "BIG");
+}
+
+void* Dstring_prototype_blink(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "BLINK");
+}
+
+void* Dstring_prototype_bold(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "B");
+}
+
+void* Dstring_prototype_fixed(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "TT");
+}
+
+void* Dstring_prototype_italics(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "I");
+}
+
+void* Dstring_prototype_small(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "SMALL");
+}
+
+void* Dstring_prototype_strike(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "STRIKE");
+}
+
+void* Dstring_prototype_sub(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "SUB");
+}
+
+void* Dstring_prototype_sup(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
+{
+    return dstring_bracket(othis, ret, "SUP");
+}
+
+
+
+/* ===================== Dstring_prototype ==================== */
+
+class Dstring_prototype : Dstring
+{
+    this(ThreadContext *tc)
+    {
+	super(tc.Dobject_prototype);
+
+	Put(TEXT_constructor, tc.Dstring_constructor, DontEnum);
+
+	static NativeFunctionData nfd[] =
+	[
+	    {   &TEXT_toString, &Dstring_prototype_toString, 0 },
+	    {   &TEXT_valueOf, &Dstring_prototype_valueOf, 0 },
+	    {   &TEXT_charAt, &Dstring_prototype_charAt, 1 },
+	    {   &TEXT_charCodeAt, &Dstring_prototype_charCodeAt, 1 },
+	    {   &TEXT_concat, &Dstring_prototype_concat, 1 },
+	    {   &TEXT_indexOf, &Dstring_prototype_indexOf, 1 },
+	    {   &TEXT_lastIndexOf, &Dstring_prototype_lastIndexOf, 1 },
+	    {   &TEXT_localeCompare, &Dstring_prototype_localeCompare, 1 },
+	    {   &TEXT_match, &Dstring_prototype_match, 1 },
+	    {   &TEXT_replace, &Dstring_prototype_replace, 2 },
+	    {   &TEXT_search, &Dstring_prototype_search, 1 },
+	    {   &TEXT_slice, &Dstring_prototype_slice, 2 },
+	    {   &TEXT_split, &Dstring_prototype_split, 2 },
+	    {   &TEXT_substr, &Dstring_prototype_substr, 2 },
+	    {   &TEXT_substring, &Dstring_prototype_substring, 2 },
+	    {   &TEXT_toLowerCase, &Dstring_prototype_toLowerCase, 0 },
+	    {   &TEXT_toLocaleLowerCase, &Dstring_prototype_toLocaleLowerCase, 0 },
+	    {   &TEXT_toUpperCase, &Dstring_prototype_toUpperCase, 0 },
+	    {   &TEXT_toLocaleUpperCase, &Dstring_prototype_toLocaleUpperCase, 0 },
+	    {   &TEXT_anchor, &Dstring_prototype_anchor, 1 },
+	    {   &TEXT_fontcolor, &Dstring_prototype_fontcolor, 1 },
+	    {   &TEXT_fontsize, &Dstring_prototype_fontsize, 1 },
+	    {   &TEXT_link, &Dstring_prototype_link, 1 },
+	    {   &TEXT_big, &Dstring_prototype_big, 0 },
+	    {   &TEXT_blink, &Dstring_prototype_blink, 0 },
+	    {   &TEXT_bold, &Dstring_prototype_bold, 0 },
+	    {   &TEXT_fixed, &Dstring_prototype_fixed, 0 },
+	    {   &TEXT_italics, &Dstring_prototype_italics, 0 },
+	    {   &TEXT_small, &Dstring_prototype_small, 0 },
+	    {   &TEXT_strike, &Dstring_prototype_strike, 0 },
+	    {   &TEXT_sub, &Dstring_prototype_sub, 0 },
+	    {   &TEXT_sup, &Dstring_prototype_sup, 0 },
+	];
+
+	DnativeFunction.init(this, nfd, DontEnum);
+    }
+}
+
+/* ===================== Dstring ==================== */
+
+class Dstring : Dobject
+{
+    this(d_string s)
+    {
+	super(getPrototype());
+	classname = TEXT_String;
+
+	Put(TEXT_length, std.utf.toUCSindex(s, s.length), DontEnum | DontDelete | ReadOnly);
+	value.putVstring(s);
+    }
+
+    this(Dobject prototype)
+    {
+	super(prototype);
+
+	classname = TEXT_String;
+	Put(TEXT_length, 0, DontEnum | DontDelete | ReadOnly);
+	value.putVstring(null);
+    }
+
+    static void init(ThreadContext *tc)
+    {
+	tc.Dstring_constructor = new Dstring_constructor(tc);
+	tc.Dstring_prototype = new Dstring_prototype(tc);
+
+	tc.Dstring_constructor.Put(TEXT_prototype, tc.Dstring_prototype, DontEnum | DontDelete | ReadOnly);
+    }
+
+    static Dfunction getConstructor()
+    {
+	ThreadContext *tc = ThreadContext.getThreadContext();
+	assert(tc);
+	return tc.Dstring_constructor;
+    }
+
+    static Dobject getPrototype()
+    {
+	ThreadContext *tc = ThreadContext.getThreadContext();
+	assert(tc);
+	return tc.Dstring_prototype;
+    }
+}