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