1 /* Digital Mars DMDScript source code.
2  * Copyright (c) 2000-2002 by Chromium Communications
3  * D version Copyright (c) 2004-2010 by Digital Mars
4  * Distributed under the Boost Software License, Version 1.0.
5  * (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  * written by Walter Bright
7  * http://www.digitalmars.com
8  *
9  * D2 port by Dmitry Olshansky 
10  *
11  * DMDScript is implemented in the D Programming Language,
12  * http://www.digitalmars.com/d/
13  *
14  * For a C++ implementation of DMDScript, including COM support, see
15  * http://www.digitalmars.com/dscript/cppscript.html
16  */
17 
18 
19 module dmdscript.dstring;
20 
21 import undead.regexp;
22 import std.utf;
23 import core.stdc.stdlib;
24 import core.stdc..string;
25 import std.exception;
26 import std.algorithm;
27 import std.range;
28 import std.stdio;
29 
30 import dmdscript.script;
31 import dmdscript.dobject;
32 import dmdscript.dregexp;
33 import dmdscript.darray;
34 import dmdscript.value;
35 import dmdscript.threadcontext;
36 import dmdscript.dfunction;
37 import dmdscript.text;
38 import dmdscript.property;
39 import dmdscript.errmsgs;
40 import dmdscript.dnative;
41 
42 //alias script.tchar tchar;
43 
44 /* ===================== Dstring_fromCharCode ==================== */
45 
46 void* Dstring_fromCharCode(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
47 {
48     static import dmdscript.utf;
49 
50     // ECMA 15.5.3.2
51     d_string s;
52 
53     for(size_t i = 0; i < arglist.length; i++)
54     {
55         Value* v;
56         uint u;
57 
58         v = &arglist[i];
59         u = v.toUint16();
60         //writef("string.fromCharCode(%x)", u);
61         if(!std.utf.isValidDchar(u))
62         {
63             ErrInfo errinfo;
64 
65             ret.putVundefined();
66             return pthis.RuntimeError(&errinfo,
67                                       errmsgtbl[ERR_NOT_VALID_UTF],
68                                       "String", "fromCharCode()",
69                                       u);
70         }
71         dmdscript.utf.encode(s, u);
72         //writefln("s[0] = %x, s = '%s'", s[0], s);
73     }
74     ret.putVstring(s);
75     return null;
76 }
77 
78 /* ===================== Dstring_constructor ==================== */
79 
80 class DstringConstructor : Dfunction
81 {
82     this()
83     {
84         super(1, Dfunction_prototype);
85         name = "String";
86 
87         static enum NativeFunctionData[] nfd =
88         [
89             { TEXT_fromCharCode, &Dstring_fromCharCode, 1 },
90         ];
91 
92         DnativeFunction.initialize(this, nfd, 0);
93     }
94 
95     override void *Construct(CallContext *cc, Value *ret, Value[] arglist)
96     {
97         // ECMA 15.5.2
98         d_string s;
99         Dobject o;
100 
101         s = (arglist.length) ? arglist[0].toString() : TEXT_;
102         o = new Dstring(s);
103         ret.putVobject(o);
104         return null;
105     }
106 
107     override void *Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist)
108     {
109         // ECMA 15.5.1
110         d_string s;
111 
112         s = (arglist.length) ? arglist[0].toString() : TEXT_;
113         ret.putVstring(s);
114         return null;
115     }
116 }
117 
118 
119 /* ===================== Dstring_prototype_toString =============== */
120 
121 void* Dstring_prototype_toString(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
122 {
123     //writef("Dstring.prototype.toString()\n");
124     // othis must be a String
125     if(!othis.isClass(TEXT_String))
126     {
127         ErrInfo errinfo;
128 
129         ret.putVundefined();
130         return pthis.RuntimeError(&errinfo,
131                                   errmsgtbl[ERR_FUNCTION_WANTS_STRING],
132                                   TEXT_toString,
133                                   othis.classname);
134     }
135     else
136     {
137         Value *v;
138 
139         v = &(cast(Dstring)othis).value;
140         Value.copy(ret, v);
141     }
142     return null;
143 }
144 
145 /* ===================== Dstring_prototype_valueOf =============== */
146 
147 void* Dstring_prototype_valueOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
148 {
149     // Does same thing as String.prototype.toString()
150 
151     //writef("string.prototype.valueOf()\n");
152     // othis must be a String
153     if(!othis.isClass(TEXT_String))
154     {
155         ErrInfo errinfo;
156 
157         ret.putVundefined();
158         return pthis.RuntimeError(&errinfo,
159                                   errmsgtbl[ERR_FUNCTION_WANTS_STRING],
160                                   TEXT_valueOf,
161                                   othis.classname);
162     }
163     else
164     {
165         Value *v;
166 
167         v = &(cast(Dstring)othis).value;
168         Value.copy(ret, v);
169     }
170     return null;
171 }
172 
173 /* ===================== Dstring_prototype_charAt =============== */
174 
175 void* Dstring_prototype_charAt(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
176 {
177     // ECMA 15.5.4.4
178 
179     Value *v;
180     int pos;            // ECMA says pos should be a d_number,
181                         // but int should behave the same
182     d_string s;
183     d_string result;
184 
185     v = &othis.value;
186     s = v.toString();
187     v = arglist.length ? &arglist[0] : &vundefined;
188     pos = cast(int)v.toInteger();
189 
190     result = TEXT_;
191 
192     if(pos >= 0)
193     {
194         size_t idx;
195 
196         while(1)
197         {
198             if(idx == s.length)
199                 break;
200             if(pos == 0)
201             {
202                 result = s[idx .. idx + std.utf.stride(s, idx)];
203                 break;
204             }
205             idx += std.utf.stride(s, idx);
206             pos--;
207         }
208     }
209 
210     ret.putVstring(result);
211     return null;
212 }
213 
214 /* ===================== Dstring_prototype_charCodeAt ============= */
215 
216 void* Dstring_prototype_charCodeAt(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
217 {
218     // ECMA 15.5.4.5
219 
220     Value *v;
221     int pos;            // ECMA says pos should be a d_number,
222                         // but int should behave the same
223     d_string s;
224     uint len;
225     d_number result;
226 
227     v = &othis.value;
228     s = v.toString();
229     v = arglist.length ? &arglist[0] : &vundefined;
230     pos = cast(int)v.toInteger();
231 
232     result = d_number.nan;
233 
234     if(pos >= 0)
235     {
236         size_t idx;
237 
238         while(1)
239         {
240             assert(idx <= s.length);
241             if(idx == s.length)
242                 break;
243             if(pos == 0)
244             {
245                 result = std.utf.decode(s, idx);
246                 break;
247             }
248             idx += std.utf.stride(s, idx);
249             pos--;
250         }
251     }
252 
253     ret.putVnumber(result);
254     return null;
255 }
256 
257 /* ===================== Dstring_prototype_concat ============= */
258 
259 void* Dstring_prototype_concat(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
260 {
261     // ECMA v3 15.5.4.6
262     d_string s;
263 
264     //writefln("Dstring.prototype.concat()");
265 
266     s = othis.value.toString();
267     for(size_t a = 0; a < arglist.length; a++)
268         s ~= arglist[a].toString();
269 
270     ret.putVstring(s);
271     return null;
272 }
273 
274 /* ===================== Dstring_prototype_indexOf ============= */
275 
276 void* Dstring_prototype_indexOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
277 {
278     import std..string : indexOf;
279     import std.utf : toUCSindex, toUTFindex;
280 
281     // ECMA 15.5.4.6
282     // String.prototype.indexOf(searchString, position)
283 
284     Value* v1;
285     Value* v2;
286     ptrdiff_t pos;            // ECMA says pos should be a d_number,
287                         // but I can't find a reason.
288     d_string s;
289     size_t sUCSdim;
290 
291     d_string searchString;
292     ptrdiff_t k;
293 
294     Value xx;
295     xx.putVobject(othis);
296     s = xx.toString();
297     sUCSdim = toUCSindex(s, s.length);
298 
299     v1 = arglist.length ? &arglist[0] : &vundefined;
300     v2 = (arglist.length >= 2) ? &arglist[1] : &vundefined;
301 
302     searchString = v1.toString();
303     pos = cast(int)v2.toInteger();
304 
305     if(pos < 0)
306         pos = 0;
307     else if(pos > sUCSdim)
308         pos = sUCSdim;
309 
310     if(searchString.length == 0)
311         k = pos;
312     else
313     {
314         pos = toUTFindex(s, pos);
315         k = indexOf(s[pos .. $], searchString);
316         if(k != -1)
317             k = toUCSindex(s, pos + k);
318     }
319 
320     ret.putVnumber(k);
321     return null;
322 }
323 
324 /* ===================== Dstring_prototype_lastIndexOf ============= */
325 
326 void* Dstring_prototype_lastIndexOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
327 {
328     import std.math : isNaN;
329     import std..string : lastIndexOf;
330     import std.utf : toUCSindex, toUTFindex;
331 
332     // ECMA v3 15.5.4.8
333     // String.prototype.lastIndexOf(searchString, position)
334 
335     Value *v1;
336     ptrdiff_t pos;            // ECMA says pos should be a d_number,
337                         // but I can't find a reason.
338     d_string s;
339     size_t sUCSdim;
340     d_string searchString;
341     ptrdiff_t k;
342 
343     version(all)
344     {
345         {
346             // This is the 'transferable' version
347             Value *v;
348             void *a;
349             v = othis.Get(TEXT_toString);
350             a = v.Call(cc, othis, ret, null);
351             if(a)                       // if exception was thrown
352                 return a;
353             s = ret.toString();
354         }
355     }
356     else
357     {
358         // the 'builtin' version
359         s = othis.value.toString();
360     }
361     sUCSdim = toUCSindex(s, s.length);
362 
363     v1 = arglist.length ? &arglist[0] : &vundefined;
364     searchString = v1.toString();
365     if(arglist.length >= 2)
366     {
367         d_number n;
368         Value *v = &arglist[1];
369 
370         n = v.toNumber();
371         if(isNaN(n) || n > sUCSdim)
372             pos = sUCSdim;
373         else if(n < 0)
374             pos = 0;
375         else
376             pos = cast(int)n;
377     }
378     else
379         pos = sUCSdim;
380 
381     //writef("len = %d, p = '%ls'\n", len, p);
382     //writef("pos = %d, sslen = %d, ssptr = '%ls'\n", pos, sslen, ssptr);
383     //writefln("s = '%s', pos = %s, searchString = '%s'", s, pos, searchString);
384 
385     if(searchString.length == 0)
386         k = pos;
387     else
388     {
389         pos = toUTFindex(s, pos);
390         pos += searchString.length;
391         if(pos > s.length)
392             pos = s.length;
393         k = lastIndexOf(s[0 .. pos], searchString);
394         //writefln("s = '%s', pos = %s, searchString = '%s', k = %d", s, pos, searchString, k);
395         if(k != -1)
396             k = toUCSindex(s, k);
397     }
398     ret.putVnumber(k);
399     return null;
400 }
401 
402 /* ===================== Dstring_prototype_localeCompare ============= */
403 
404 void* Dstring_prototype_localeCompare(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
405 {
406     // ECMA v3 15.5.4.9
407     d_string s1;
408     d_string s2;
409     d_number n;
410     Value *v;
411 
412     v = &othis.value;
413     s1 = v.toString();
414     s2 = arglist.length ? arglist[0].toString() : vundefined.toString();
415     n = localeCompare(cc, s1, s2);
416     ret.putVnumber(n);
417     return null;
418 }
419 
420 /* ===================== Dstring_prototype_match ============= */
421 
422 void* Dstring_prototype_match(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
423 {
424     // ECMA v3 15.5.4.10
425     Dregexp r;
426     Dobject o;
427 
428     if(arglist.length && !arglist[0].isPrimitive() &&
429        (o = arglist[0].toObject()).isDregexp())
430     {
431     }
432     else
433     {
434         Value regret;
435 
436         regret.putVobject(null);
437         Dregexp.getConstructor().Construct(cc, &regret, arglist);
438         o = regret.object;
439     }
440 
441     r = cast(Dregexp)o;
442     if(r.global.dbool)
443     {
444         Darray a = new Darray;
445         d_int32 n;
446         d_int32 i;
447         d_int32 lasti;
448 
449         i = 0;
450         lasti = 0;
451         for(n = 0;; n++)
452         {
453             r.lastIndex.putVnumber(i);
454             Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_STRING);
455             if(!ret..string)             // if match failed
456             {
457                 r.lastIndex.putVnumber(i);
458                 break;
459             }
460             lasti = i;
461             i = cast(d_int32)r.lastIndex.toInt32();
462             if(i == lasti)              // if no source was consumed
463                 i++;                    // consume a character
464 
465             a.Put(n, ret, 0);           // a[n] = ret;
466         }
467         ret.putVobject(a);
468     }
469     else
470     {
471         Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_ARRAY);
472     }
473     return null;
474 }
475 
476 /* ===================== Dstring_prototype_replace ============= */
477 
478 void* Dstring_prototype_replace(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
479 {
480     import std..string : indexOf;
481     // ECMA v3 15.5.4.11
482     // String.prototype.replace(searchValue, replaceValue)
483 
484     d_string string;
485     d_string searchString;
486     d_string newstring;
487     Value *searchValue;
488     Value *replaceValue;
489     Dregexp r;
490     RegExp re;
491     d_string replacement;
492     d_string result;
493     int m;
494     int i;
495     int lasti;
496     regmatch_t[1] pmatch;
497     Dfunction f;
498     Value* v;
499 
500     v = &othis.value;
501     string = v.toString();
502     searchValue = (arglist.length >= 1) ? &arglist[0] : &vundefined;
503     replaceValue = (arglist.length >= 2) ? &arglist[1] : &vundefined;
504     r = Dregexp.isRegExp(searchValue);
505     f = Dfunction.isFunction(replaceValue);
506     if(r)
507     {
508         int offset = 0;
509 
510         re = r.re;
511         i = 0;
512         result = string;
513 
514         r.lastIndex.putVnumber(0);
515         for(;; )
516         {
517             Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_STRING);
518             if(!ret..string)             // if match failed
519                 break;
520 
521             m = re.re_nsub;
522             if(f)
523             {
524                 Value* alist;
525 
526                 alist = cast(Value *)alloca((m + 3) * Value.sizeof);
527                 assert(alist);
528                 alist[0].putVstring(ret..string);
529                 for(i = 0; i < m; i++)
530                 {
531                     alist[1 + i].putVstring(
532                         string[re.pmatch[1 + i].rm_so .. re.pmatch[1 + i].rm_eo]);
533                 }
534                 alist[m + 1].putVnumber(re.pmatch[0].rm_so);
535                 alist[m + 2].putVstring(string);
536                 f.Call(cc, f, ret, alist[0 .. m + 3]);
537                 replacement = ret.toString();
538             }
539             else
540             {
541                 newstring = replaceValue.toString();
542                 replacement = re.replace(newstring);
543             }
544             ptrdiff_t starti = re.pmatch[0].rm_so + offset;
545             ptrdiff_t endi = re.pmatch[0].rm_eo + offset;
546             result = string[0 .. starti] ~
547                      replacement ~
548                      string[endi .. $];
549 
550             if(re.attributes & RegExp.REA.global)
551             {
552                 offset += replacement.length - (endi - starti);
553 
554                 // If no source was consumed, consume a character
555                 lasti = i;
556                 i = cast(d_int32)r.lastIndex.toInt32();
557                 if(i == lasti)
558                 {
559                     i++;
560                     r.lastIndex.putVnumber(i);
561                 }
562             }
563             else
564                 break;
565         }
566     }
567     else
568     {
569         searchString = searchValue.toString();
570         ptrdiff_t match = indexOf(string, searchString);
571         if(match >= 0)
572         {
573             pmatch[0].rm_so = match;
574             pmatch[0].rm_eo = match + searchString.length;
575             if(f)
576             {
577                 Value[3] alist;
578 
579                 alist[0].putVstring(searchString);
580                 alist[1].putVnumber(pmatch[0].rm_so);
581                 alist[2].putVstring(string);
582                 f.Call(cc, f, ret, alist);
583                 replacement = ret.toString();
584             }
585             else
586             {
587                 newstring = replaceValue.toString();
588                 replacement = RegExp.replace3(newstring, string, pmatch);
589             }
590             result = string[0 .. match] ~
591                      replacement ~
592                      string[match + searchString.length .. $];
593         }
594         else
595         {
596             result = string;
597         }
598     }
599 
600     ret.putVstring(result);
601     return null;
602 }
603 
604 /* ===================== Dstring_prototype_search ============= */
605 
606 void* Dstring_prototype_search(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
607 {
608     // ECMA v3 15.5.4.12
609     Dregexp r;
610     Dobject o;
611 
612     //writef("String.prototype.search()\n");
613     if(arglist.length && !arglist[0].isPrimitive() &&
614        (o = arglist[0].toObject()).isDregexp())
615     {
616     }
617     else
618     {
619         Value regret;
620 
621         regret.putVobject(null);
622         Dregexp.getConstructor().Construct(cc, &regret, arglist);
623         o = regret.object;
624     }
625 
626     r = cast(Dregexp)o;
627     Dregexp.exec(r, ret, (&othis.value)[0 .. 1], EXEC_INDEX);
628     return null;
629 }
630 
631 /* ===================== Dstring_prototype_slice ============= */
632 
633 void* Dstring_prototype_slice(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
634 {
635     // ECMA v3 15.5.4.13
636     ptrdiff_t start;
637     ptrdiff_t end;
638     ptrdiff_t sUCSdim;
639     d_string s;
640     d_string r;
641     Value *v;
642 
643     v = &othis.value;
644     s = v.toString();
645     sUCSdim = std.utf.toUCSindex(s, s.length);
646     switch(arglist.length)
647     {
648     case 0:
649         start = 0;
650         end = sUCSdim;
651         break;
652 
653     case 1:
654         start = arglist[0].toInt32();
655         end = sUCSdim;
656         break;
657 
658     default:
659         start = arglist[0].toInt32();
660         end = arglist[1].toInt32();
661         break;
662     }
663 
664     if(start < 0)
665     {
666         start += sUCSdim;
667         if(start < 0)
668             start = 0;
669     }
670     else if(start >= sUCSdim)
671         start = sUCSdim;
672 
673     if(end < 0)
674     {
675         end += sUCSdim;
676         if(end < 0)
677             end = 0;
678     }
679     else if(end >= sUCSdim)
680         end = sUCSdim;
681 
682     if(start > end)
683         end = start;
684 
685     start = toUTFindex(s, start);
686     end = toUTFindex(s, end);
687     r = s[start .. end];
688 
689     ret.putVstring(r);
690     return null;
691 }
692 
693 
694 /* ===================== Dstring_prototype_split ============= */
695 
696 void* Dstring_prototype_split(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
697 {
698     // ECMA v3 15.5.4.14
699     // String.prototype.split(separator, limit)
700     size_t lim;
701     size_t p;
702     size_t q;
703     size_t e;
704     Value* separator = &vundefined;
705     Value* limit = &vundefined;
706     Dregexp R;
707     RegExp re;
708     d_string rs;
709     d_string T;
710     d_string S;
711     Darray A;
712     int str;
713 
714     //writefln("Dstring_prototype_split()");
715     switch(arglist.length)
716     {
717     default:
718         limit = &arglist[1];
719         goto case;
720     case 1:
721         separator = &arglist[0];
722         goto case;
723     case 0:
724         break;
725     }
726 
727     Value *v;
728     v = &othis.value;
729     S = v.toString();
730     A = new Darray;
731     if(limit.isUndefined())
732         lim = ~0u;
733     else
734         lim = limit.toUint32();
735     p = 0;
736     R = Dregexp.isRegExp(separator);
737     if(R)       // regular expression
738     {
739         re = R.re;
740         assert(re);
741         rs = null;
742         str = 0;
743     }
744     else        // string
745     {
746         re = null;
747         rs = separator.toString();
748         str = 1;
749     }
750     if(lim == 0)
751         goto Lret;
752 
753     // ECMA v3 15.5.4.14 is specific: "If separator is undefined, then the
754     // result array contains just one string, which is the this value
755     // (converted to a string)." However, neither Javascript nor Jscript
756     // do that, they regard an undefined as being the string "undefined".
757     // We match Javascript/Jscript behavior here, not ECMA.
758 
759     // Uncomment for ECMA compatibility
760     //if (!separator.isUndefined())
761     {
762         //writefln("test1 S = '%s', rs = '%s'", S, rs);
763         if(S.length)
764         {
765             L10:
766             for(q = p; q != S.length; q++)
767             {
768                 if(str)                 // string
769                 {
770                     if(q + rs.length <= S.length && !memcmp(S.ptr + q, rs.ptr, rs.length * tchar.sizeof))
771                     {
772                         e = q + rs.length;
773                         if(e != p)
774                         {
775                             T = S[p .. q];
776                             A.Put(cast(uint)A.length.number, T, 0);
777                             if(A.length.number == lim)
778                                 goto Lret;
779                             p = e;
780                             goto L10;
781                         }
782                     }
783                 }
784                 else            // regular expression
785                 {
786                     if(re.test(S, q))
787                     {
788                         q = re.pmatch[0].rm_so;
789                         e = re.pmatch[0].rm_eo;
790                         if(e != p)
791                         {
792                             T = S[p .. q];
793                             //writefln("S = '%s', T = '%s', p = %d, q = %d, e = %d\n", S, T, p, q, e);
794                             A.Put(cast(uint)A.length.number, T, 0);
795                             if(A.length.number == lim)
796                                 goto Lret;
797                             p = e;
798                             for(uint i = 0; i < re.re_nsub; i++)
799                             {
800                                 ptrdiff_t so = re.pmatch[1 + i].rm_so;
801                                 ptrdiff_t eo = re.pmatch[1 + i].rm_eo;
802 
803                                 //writefln("i = %d, nsub = %s, so = %s, eo = %s, S.length = %s", i, re.re_nsub, so, eo, S.length);
804                                 if(so != -1 && eo != -1)
805                                     T = S[so .. eo];
806                                 else
807                                     T = null;
808                                 A.Put(cast(uint)A.length.number, T, 0);
809                                 if(A.length.number == lim)
810                                     goto Lret;
811                             }
812                             goto L10;
813                         }
814                     }
815                 }
816             }
817             T = S[p .. S.length];
818             A.Put(cast(uint)A.length.number, T, 0);
819             goto Lret;
820         }
821         if(str)                 // string
822         {
823             if(rs.length <= S.length && S[0 .. rs.length] == rs[])
824                 goto Lret;
825         }
826         else            // regular expression
827         {
828             if(re.test(S, 0))
829                 goto Lret;
830         }
831     }
832 
833     A.Put(0u, S, 0);
834     Lret:
835     ret.putVobject(A);
836     return null;
837 }
838 
839 
840 /* ===================== Dstring_prototype_substr ============= */
841 
842 void *dstring_substring(d_string s, size_t sUCSdim, d_number start, d_number end, Value *ret)
843 {
844     import std.math : isNaN;
845     import std.utf : toUTFindex;
846 
847     d_string sb;
848     d_int32 sb_len;
849 
850     if(isNaN(start))
851         start = 0;
852     else if(start > sUCSdim)
853         start = sUCSdim;
854     else if(start < 0)
855         start = 0;
856 
857     if(isNaN(end))
858         end = 0;
859     else if(end > sUCSdim)
860         end = sUCSdim;
861     else if(end < 0)
862         end = 0;
863 
864     if(end < start)             // swap
865     {
866         d_number t;
867 
868         t = start;
869         start = end;
870         end = t;
871     }
872 
873     size_t st = toUTFindex(s, cast(size_t)start);
874     size_t en = toUTFindex(s, cast(size_t)end);
875     sb = s[st .. en];
876 
877     ret.putVstring(sb);
878     return null;
879 }
880 
881 void* Dstring_prototype_substr(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
882 {
883     import std.math : isNaN;
884     import std.utf : toUCSindex;
885 
886     // Javascript: TDG pg. 689
887     // String.prototype.substr(start, length)
888     d_number start;
889     d_number length;
890     d_string s;
891 
892     s = othis.value.toString();
893     size_t sUCSdim = toUCSindex(s, s.length);
894     start = 0;
895     length = 0;
896     if(arglist.length >= 1)
897     {
898         start = arglist[0].toInteger();
899         if(start < 0)
900             start = sUCSdim + start;
901         if(arglist.length >= 2)
902         {
903             length = arglist[1].toInteger();
904             if(isNaN(length) || length < 0)
905                 length = 0;
906         }
907         else
908             length = sUCSdim - start;
909     }
910 
911     return dstring_substring(s, sUCSdim, start, start + length, ret);
912 }
913 
914 /* ===================== Dstring_prototype_substring ============= */
915 
916 void* Dstring_prototype_substring(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
917 {
918     import std.utf : toUCSindex;
919 
920     // ECMA 15.5.4.9
921     // String.prototype.substring(start)
922     // String.prototype.substring(start, end)
923     d_number start;
924     d_number end;
925     d_string s;
926 
927     //writefln("String.prototype.substring()");
928     s = othis.value.toString();
929     size_t sUCSdim = toUCSindex(s, s.length);
930     start = 0;
931     end = sUCSdim;
932     if(arglist.length >= 1)
933     {
934         start = arglist[0].toInteger();
935         if(arglist.length >= 2)
936             end = arglist[1].toInteger();
937         //writef("s = '%ls', start = %d, end = %d\n", s, start, end);
938     }
939 
940     void* p = dstring_substring(s, sUCSdim, start, end, ret);
941     return p;
942 }
943 
944 /* ===================== Dstring_prototype_toLowerCase ============= */
945 
946 enum CASE
947 {
948     Lower,
949     Upper,
950     LocaleLower,
951     LocaleUpper
952 };
953 
954 void *tocase(Dobject othis, Value *ret, CASE caseflag)
955 {
956     import std..string : toLower, toUpper;
957 
958     d_string s;
959 
960     s = othis.value.toString();
961     switch(caseflag)
962     {
963     case CASE.Lower:
964         s = toLower(s);
965         break;
966     case CASE.Upper:
967         s = toUpper(s);
968         break;
969     case CASE.LocaleLower:
970         s = toLower(s);
971         break;
972     case CASE.LocaleUpper:
973         s = toUpper(s);
974         break;
975     default:
976         assert(0);
977     }
978 
979     ret.putVstring(s);
980     return null;
981 }
982 
983 void* Dstring_prototype_toLowerCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
984 {
985     // ECMA 15.5.4.11
986     // String.prototype.toLowerCase()
987 
988     //writef("Dstring_prototype_toLowerCase()\n");
989     return tocase(othis, ret, CASE.Lower);
990 }
991 
992 /* ===================== Dstring_prototype_toLocaleLowerCase ============= */
993 
994 void* Dstring_prototype_toLocaleLowerCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
995 {
996     // ECMA v3 15.5.4.17
997 
998     //writef("Dstring_prototype_toLocaleLowerCase()\n");
999     return tocase(othis, ret, CASE.LocaleLower);
1000 }
1001 
1002 /* ===================== Dstring_prototype_toUpperCase ============= */
1003 
1004 void* Dstring_prototype_toUpperCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1005 {
1006     // ECMA 15.5.4.12
1007     // String.prototype.toUpperCase()
1008 
1009     return tocase(othis, ret, CASE.Upper);
1010 }
1011 
1012 /* ===================== Dstring_prototype_toLocaleUpperCase ============= */
1013 
1014 void* Dstring_prototype_toLocaleUpperCase(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1015 {
1016     // ECMA v3 15.5.4.18
1017 
1018     return tocase(othis, ret, CASE.LocaleUpper);
1019 }
1020 
1021 /* ===================== Dstring_prototype_anchor ============= */
1022 
1023 void *dstring_anchor(Dobject othis, Value* ret, d_string tag, d_string name, Value[] arglist)
1024 {
1025     // For example:
1026     //	"foo".anchor("bar")
1027     // produces:
1028     //	<tag name="bar">foo</tag>
1029 
1030     d_string foo = othis.value.toString();
1031     Value* va = arglist.length ? &arglist[0] : &vundefined;
1032     d_string bar = va.toString();
1033 
1034     d_string s;
1035 
1036     s = "<"     ~
1037         tag     ~
1038         " "     ~
1039         name    ~
1040         "=\""   ~
1041         bar     ~
1042         "\">"   ~
1043         foo     ~
1044         "</"    ~
1045         tag     ~
1046         ">";
1047 
1048     ret.putVstring(s);
1049     return null;
1050 }
1051 
1052 
1053 void* Dstring_prototype_anchor(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1054 {
1055     // Non-standard extension
1056     // String.prototype.anchor(anchor)
1057     // For example:
1058     //	"foo".anchor("bar")
1059     // produces:
1060     //	<A NAME="bar">foo</A>
1061 
1062     return dstring_anchor(othis, ret, "A", "NAME", arglist);
1063 }
1064 
1065 void* Dstring_prototype_fontcolor(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1066 {
1067     return dstring_anchor(othis, ret, "FONT", "COLOR", arglist);
1068 }
1069 
1070 void* Dstring_prototype_fontsize(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1071 {
1072     return dstring_anchor(othis, ret, "FONT", "SIZE", arglist);
1073 }
1074 
1075 void* Dstring_prototype_link(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1076 {
1077     return dstring_anchor(othis, ret, "A", "HREF", arglist);
1078 }
1079 
1080 
1081 /* ===================== Dstring_prototype bracketing ============= */
1082 
1083 /***************************
1084  * Produce <tag>othis</tag>
1085  */
1086 
1087 void *dstring_bracket(Dobject othis, Value* ret, d_string tag)
1088 {
1089     d_string foo = othis.value.toString();
1090     d_string s;
1091 
1092     s = "<"     ~
1093         tag     ~
1094         ">"     ~
1095         foo     ~
1096         "</"    ~
1097         tag     ~
1098         ">";
1099 
1100     ret.putVstring(s);
1101     return null;
1102 }
1103 
1104 void* Dstring_prototype_big(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1105 {
1106     // Non-standard extension
1107     // String.prototype.big()
1108     // For example:
1109     //	"foo".big()
1110     // produces:
1111     //	<BIG>foo</BIG>
1112 
1113     return dstring_bracket(othis, ret, "BIG");
1114 }
1115 
1116 void* Dstring_prototype_blink(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1117 {
1118     return dstring_bracket(othis, ret, "BLINK");
1119 }
1120 
1121 void* Dstring_prototype_bold(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1122 {
1123     return dstring_bracket(othis, ret, "B");
1124 }
1125 
1126 void* Dstring_prototype_fixed(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1127 {
1128     return dstring_bracket(othis, ret, "TT");
1129 }
1130 
1131 void* Dstring_prototype_italics(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1132 {
1133     return dstring_bracket(othis, ret, "I");
1134 }
1135 
1136 void* Dstring_prototype_small(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1137 {
1138     return dstring_bracket(othis, ret, "SMALL");
1139 }
1140 
1141 void* Dstring_prototype_strike(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1142 {
1143     return dstring_bracket(othis, ret, "STRIKE");
1144 }
1145 
1146 void* Dstring_prototype_sub(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1147 {
1148     return dstring_bracket(othis, ret, "SUB");
1149 }
1150 
1151 void* Dstring_prototype_sup(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
1152 {
1153     return dstring_bracket(othis, ret, "SUP");
1154 }
1155 
1156 
1157 
1158 /* ===================== Dstring_prototype ==================== */
1159 
1160 class DstringPrototype : Dstring
1161 {
1162     this()
1163     {
1164         super(Dobject_prototype);
1165 
1166         Put(TEXT_constructor, Dstring_constructor, DontEnum);
1167 
1168         static enum NativeFunctionData[] nfd =
1169         [
1170             { TEXT_toString, &Dstring_prototype_toString, 0 },
1171             { TEXT_valueOf, &Dstring_prototype_valueOf, 0 },
1172             { TEXT_charAt, &Dstring_prototype_charAt, 1 },
1173             { TEXT_charCodeAt, &Dstring_prototype_charCodeAt, 1 },
1174             { TEXT_concat, &Dstring_prototype_concat, 1 },
1175             { TEXT_indexOf, &Dstring_prototype_indexOf, 1 },
1176             { TEXT_lastIndexOf, &Dstring_prototype_lastIndexOf, 1 },
1177             { TEXT_localeCompare, &Dstring_prototype_localeCompare, 1 },
1178             { TEXT_match, &Dstring_prototype_match, 1 },
1179             { TEXT_replace, &Dstring_prototype_replace, 2 },
1180             { TEXT_search, &Dstring_prototype_search, 1 },
1181             { TEXT_slice, &Dstring_prototype_slice, 2 },
1182             { TEXT_split, &Dstring_prototype_split, 2 },
1183             { TEXT_substr, &Dstring_prototype_substr, 2 },
1184             { TEXT_substring, &Dstring_prototype_substring, 2 },
1185             { TEXT_toLowerCase, &Dstring_prototype_toLowerCase, 0 },
1186             { TEXT_toLocaleLowerCase, &Dstring_prototype_toLocaleLowerCase, 0 },
1187             { TEXT_toUpperCase, &Dstring_prototype_toUpperCase, 0 },
1188             { TEXT_toLocaleUpperCase, &Dstring_prototype_toLocaleUpperCase, 0 },
1189             { TEXT_anchor, &Dstring_prototype_anchor, 1 },
1190             { TEXT_fontcolor, &Dstring_prototype_fontcolor, 1 },
1191             { TEXT_fontsize, &Dstring_prototype_fontsize, 1 },
1192             { TEXT_link, &Dstring_prototype_link, 1 },
1193             { TEXT_big, &Dstring_prototype_big, 0 },
1194             { TEXT_blink, &Dstring_prototype_blink, 0 },
1195             { TEXT_bold, &Dstring_prototype_bold, 0 },
1196             { TEXT_fixed, &Dstring_prototype_fixed, 0 },
1197             { TEXT_italics, &Dstring_prototype_italics, 0 },
1198             { TEXT_small, &Dstring_prototype_small, 0 },
1199             { TEXT_strike, &Dstring_prototype_strike, 0 },
1200             { TEXT_sub, &Dstring_prototype_sub, 0 },
1201             { TEXT_sup, &Dstring_prototype_sup, 0 },
1202         ];
1203 
1204         DnativeFunction.initialize(this, nfd, DontEnum);
1205     }
1206 }
1207 
1208 /* ===================== Dstring ==================== */
1209 
1210 class Dstring : Dobject
1211 {
1212     this(d_string s)
1213     {
1214         super(getPrototype());
1215         classname = TEXT_String;
1216 
1217         Put(TEXT_length, std.utf.toUCSindex(s, s.length), DontEnum | DontDelete | ReadOnly);
1218         value.putVstring(s);
1219     }
1220 
1221     this(Dobject prototype)
1222     {
1223         super(prototype);
1224 
1225         classname = TEXT_String;
1226         Put(TEXT_length, 0, DontEnum | DontDelete | ReadOnly);
1227         value.putVstring(null);
1228     }
1229 
1230     static void initialize()
1231     {
1232         Dstring_constructor = new DstringConstructor();
1233         Dstring_prototype = new DstringPrototype();
1234 
1235         Dstring_constructor.Put(TEXT_prototype, Dstring_prototype, DontEnum | DontDelete | ReadOnly);
1236     }
1237 
1238     static Dfunction getConstructor()
1239     {
1240         return Dstring_constructor;
1241     }
1242 
1243     static Dobject getPrototype()
1244     {
1245         return Dstring_prototype;
1246     }
1247 }