v2 / vlib / builtin / js / string.js.v
1189 lines · 1041 sloc · 25.81 KB · 74faf2305c5d18430df7ddd30ac8252547c85838
Raw
1module builtin
2
3import strings
4
5pub struct string {
6pub:
7 str JS.String
8 len int
9}
10
11interface TosSource {}
12
13pub fn (s string) runes() []rune {
14 ret := JS.makeEmptyArray()
15 #for (r of s.str) array_push(ret,new rune(r),false);
16
17 return ret
18}
19
20pub fn (s string) slice(a int, b int) string {
21 return string(s.str.slice(JS.Number(a), JS.Number(b)))
22}
23
24pub fn (s string) substr(start int, end int) string {
25 return s.slice(start, end)
26}
27
28pub fn (s string) after(dot string) string {
29 return string(s.str.slice(JS.Number(int(s.str.lastIndexOf(dot.str)) + 1), s.str.length))
30}
31
32pub fn (s string) after_char(dot u8) string {
33 // TODO: Implement after byte
34 return s
35}
36
37pub fn (s string) all_after(dot string) string {
38 pos := if dot.len == 0 { -1 } else { int(s.str.indexOf(dot.str)) }
39 if pos == -1 {
40 return s.clone()
41 }
42 return s[pos + dot.len..]
43}
44
45// why does this exist?
46pub fn (s string) all_after_last(dot string) string {
47 pos := if dot.len == 0 { -1 } else { int(s.str.lastIndexOf(dot.str)) }
48 if pos == -1 {
49 return s.clone()
50 }
51 return s[pos + dot.len..]
52}
53
54pub fn (s string) all_before(dot string) string {
55 pos := if dot.len == 0 { -1 } else { int(s.str.indexOf(dot.str)) }
56 if pos == -1 {
57 return s.clone()
58 }
59 return s[..pos]
60 // return string(s.str.slice(0, s.str.indexOf(dot.str)))
61}
62
63pub fn (s string) all_before_last(dot string) string {
64 pos := if dot.len == 0 { -1 } else { int(s.str.lastIndexOf(dot.str)) }
65 if pos == -1 {
66 return s.clone()
67 }
68 return s[..pos]
69}
70
71pub fn (s string) bool() bool {
72 return s == 'true'
73}
74
75pub fn (s string) split(dot string) []string {
76 tmparr := s.str.split(dot.str).map(fn (it JS.Any) JS.Any {
77 res := ''
78 #res.str = it
79
80 return res
81 })
82 _ := tmparr
83 mut arr := []string{}
84 #arr = new array(new array_buffer({arr: tmparr,index_start: new int(0),len: new int(tmparr.length)}))
85
86 return arr
87}
88
89pub fn (s string) split_any(delim string) []string {
90 if delim.len == 0 {
91 return s.split(delim)
92 }
93
94 mut pattern := delim
95
96 // we use a regex with a bracket expression to match any of the characters in delim
97 // so we need to prevent the caller from escaping the regex
98 // to do this we escape any `]`, and remove all `\` while adding an escaped `\\`
99 // back if the original string contained any
100 if pattern.contains('\\') {
101 pattern = pattern.replace('\\', '')
102 pattern = '${pattern}\\\\'
103 }
104 pattern = pattern.replace(']', '\\]')
105
106 mut regexp := JS.RegExp{}
107 #regexp = new RegExp('[' + pattern.str + ']', 'g')
108
109 tmparr := s.str.split(regexp).map(fn (it JS.Any) JS.Any {
110 res := ''
111 #res.str = it
112
113 return res
114 })
115 _ := tmparr
116
117 mut arr := []string{}
118 #arr = new array(new array_buffer({arr: tmparr,index_start: new int(0),len: new int(tmparr.length)}))
119
120 // FIXME: ugly hack to handle edge case where the last character in the string is
121 // one of the delimiters to match V behavior
122 #if (s.len > 0 && pattern.str.includes(s.str[s.len - 1])) {
123 arr.pop()
124 #}
125
126 return arr
127}
128
129pub fn (s string) bytes() []u8 {
130 sep := ''
131 tmparr := s.str.split(sep.str).map(fn (it JS.Any) JS.Any {
132 return JS.Any(u8(JS.String(it).charCodeAt(JS.Number(0))))
133 })
134 _ := tmparr
135 mut arr := []u8{}
136 #arr = new array(new array_buffer({arr: tmparr,index_start: new int(0),len: new int(tmparr.length)}))
137
138 return arr
139}
140
141pub fn (s string) capitalize() string {
142 part := string(s.str.slice(JS.Number(1), s.str.length))
143 return string(s.str.charAt(JS.Number(0)).toUpperCase().concat(part.str))
144}
145
146pub fn (s string) clone() string {
147 return string(s.str)
148}
149
150// contains returns `true` if the string contains `substr`.
151// See also: [`string.index`](#string.index)
152pub fn (s string) contains(substr string) bool {
153 return bool(s.str.includes(substr.str))
154}
155
156// contains_any returns `true` if the string contains any chars in `chars`.
157pub fn (s string) contains_any(chars string) bool {
158 sep := ''
159 res := chars.str.split(sep.str)
160 for i in 0 .. int(res.length) {
161 if bool(s.str.includes(JS.String(res.at(JS.Number(i))))) {
162 return true
163 }
164 }
165 return false
166}
167
168// contains_only returns `true`, if the string contains only the characters in `chars`.
169pub fn (s string) contains_only(chars string) bool {
170 if chars.len == 0 {
171 return false
172 }
173 for ch in s {
174 mut res := 0
175 for c in chars {
176 if ch == c {
177 res++
178 break
179 }
180 }
181 if res == 0 {
182 return false
183 }
184 }
185 return true
186}
187
188pub fn (s string) contains_any_substr(chars []string) bool {
189 if chars.len == 0 {
190 return true
191 }
192 for x in chars {
193 if bool(s.str.includes(x.str)) {
194 return true
195 }
196 }
197 return false
198}
199
200pub fn (s string) count(substr string) int {
201 // TODO: "error: `[]JS.String` is not a struct" when returning arr.length or arr.len
202 arr := s.str.split(substr.str)
203 len := int(arr.length)
204 if len == 0 {
205 return 0
206 } else {
207 return len - 1
208 }
209}
210
211pub fn (s string) ends_with(p string) bool {
212 mut res := false
213 #res.val = s.str.endsWith(p.str)
214
215 return res
216}
217
218pub fn (s string) starts_with(p string) bool {
219 return bool(s.str.startsWith(p.str))
220}
221
222pub fn (s string) fields() []string {
223 mut res := []string{}
224 mut word_start := 0
225 mut word_len := 0
226 mut is_in_word := false
227 mut is_space := false
228 for i, c in s {
229 is_space = c in [32, 9, 10]
230 if !is_space {
231 word_len++
232 }
233 if !is_in_word && !is_space {
234 word_start = i
235 is_in_word = true
236 continue
237 }
238 if is_space && is_in_word {
239 res << s[word_start..word_start + word_len]
240 is_in_word = false
241 word_len = 0
242 word_start = 0
243 continue
244 }
245 }
246 if is_in_word && word_len > 0 {
247 // collect the remainder word at the end
248 res << s[word_start..s.len]
249 }
250 return res
251}
252
253pub fn (s string) find_between(start string, end string) string {
254 return string(s.str.slice(JS.Number(int(s.str.indexOf(start.str)) + 1), s.str.indexOf(end.str)))
255}
256
257// unnecessary in the JS backend, implemented for api parity.
258pub fn (s &string) free() {}
259
260pub fn (s string) hash() int {
261 mut h := u32(0)
262 if h == 0 && s.len > 0 {
263 for c in s {
264 h = h * 31 + u32(c)
265 }
266 }
267 return int(h)
268}
269
270// int returns the value of the string as an integer `'1'.int() == 1`.
271pub fn (s string) int() int {
272 res := int(0)
273 #if (typeof(s) == "string") { res.val = parseInt(s) }
274 #else { res.val = parseInt(s.str) }
275
276 return res
277}
278
279// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`.
280pub fn (s string) i64() i64 {
281 mut res := i64(0)
282 #res = new i64(BigInt(s.str))
283
284 return res
285}
286
287// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`.
288pub fn (s string) i8() i8 {
289 return i8(JS.parseInt(s.str))
290}
291
292// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`.
293pub fn (s string) i16() i16 {
294 return i16(JS.parseInt(s.str))
295}
296
297// i32 returns the value of the string as i32 `'1'.i32() == i32(1)`.
298pub fn (s string) i32() i32 {
299 return i32(JS.parseInt(s.str))
300}
301
302// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`.
303pub fn (s string) f32() f32 {
304 // return C.atof(&char(s.str))
305 return f32(JS.parseFloat(s.str))
306}
307
308// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`.
309pub fn (s string) f64() f64 {
310 return f64(JS.parseFloat(s.str))
311}
312
313// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`.
314pub fn (s string) u16() u16 {
315 return u16(JS.parseInt(s.str))
316}
317
318// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`.
319pub fn (s string) u32() u32 {
320 return u32(JS.parseInt(s.str))
321}
322
323// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`.
324pub fn (s string) u64() u64 {
325 mut res := u64(0)
326 #res = new u64(BigInt(s.str))
327
328 return res
329}
330
331pub fn (s string) u8() u8 {
332 return u8(JS.parseInt(s.str))
333}
334
335// trim_right strips any of the characters given in `cutset` from the right of the string.
336// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V'
337@[direct_array_access]
338pub fn (s string) trim_right(cutset string) string {
339 if s == '' || cutset == '' {
340 return s.clone()
341 }
342
343 mut pos := s.len - 1
344
345 for pos >= 0 {
346 mut found := false
347 for cs in cutset {
348 if s[pos] == cs {
349 found = true
350 }
351 }
352 if !found {
353 break
354 }
355 pos--
356 }
357
358 if pos < 0 {
359 return ''
360 }
361
362 return s[..pos + 1]
363}
364
365// trim_left strips any of the characters given in `cutset` from the left of the string.
366// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer'
367@[direct_array_access]
368pub fn (s string) trim_left(cutset string) string {
369 if s == '' || cutset == '' {
370 return s.clone()
371 }
372 mut pos := 0
373 for pos < s.len {
374 mut found := false
375 for cs in cutset {
376 if s[pos] == cs {
377 found = true
378 break
379 }
380 }
381 if !found {
382 break
383 }
384 pos++
385 }
386 return s[pos..]
387}
388
389// trim_string_left strips `str` from the start of the string.
390// Example: assert 'WorldHello V'.trim_string_left('World') == 'Hello V'
391pub fn (s string) trim_string_left(str string) string {
392 if s.starts_with(str) {
393 return s[str.len..]
394 }
395 return s.clone()
396}
397
398// trim_string_right strips `str` from the end of the string.
399// Example: assert 'Hello VWorld'.trim_string_right('World') == 'Hello V'
400pub fn (s string) trim_string_right(str string) string {
401 if s.ends_with(str) {
402 return s[..s.len - str.len]
403 }
404 return s.clone()
405}
406
407// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`.
408pub fn compare_strings(a &string, b &string) int {
409 if a < b {
410 return -1
411 }
412 if a > b {
413 return 1
414 }
415 return 0
416}
417
418// compare_strings_reverse returns `1` if `a < b`, `-1` if `a > b` else `0`.
419fn compare_strings_reverse(a &string, b &string) int {
420 if a < b {
421 return 1
422 }
423 if a > b {
424 return -1
425 }
426 return 0
427}
428
429// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`.
430fn compare_strings_by_len(a &string, b &string) int {
431 if a.len < b.len {
432 return -1
433 }
434 if a.len > b.len {
435 return 1
436 }
437 return 0
438}
439
440// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing.
441fn compare_lower_strings(a &string, b &string) int {
442 aa := a.to_lower()
443 bb := b.to_lower()
444 return compare_strings(&aa, &bb)
445}
446
447// at returns the byte at index `idx`.
448// Example: assert 'ABC'.at(1) == u8(`B`)
449fn (s string) at(idx int) u8 {
450 mut result := u8(0)
451 #result = new u8(s.str.charCodeAt(result))
452
453 return result
454}
455
456pub fn (s string) to_lower() string {
457 mut result := ''
458 #let str = s.str.toLowerCase()
459 #result = new string(str)
460
461 return result
462}
463
464// TODO: check if that behaves the same as V's own string.replace(old_sub,new_sub):
465pub fn (s string) replace(old_sub string, new_sub string) string {
466 mut result := ''
467 #result = new string( s.str.replaceAll(old_sub.str, new_sub.str) )
468
469 return result
470}
471
472pub fn (s string) to_upper() string {
473 mut result := ''
474 #let str = s.str.toUpperCase()
475 #result = new string(str)
476
477 return result
478}
479
480// sort sorts the string array.
481pub fn (mut s []string) sort() {
482 s.sort_with_compare(compare_strings)
483}
484
485// sort_ignore_case sorts the string array using case insensitive comparing.
486pub fn (mut s []string) sort_ignore_case() {
487 s.sort_with_compare(compare_lower_strings)
488}
489
490// sort_by_len sorts the string array by each string's `.len` length.
491pub fn (mut s []string) sort_by_len() {
492 s.sort_with_compare(compare_strings_by_len)
493}
494
495// str returns a copy of the string.
496pub fn (s string) str() string {
497 return s.clone()
498}
499
500pub fn (s string) repeat(count int) string {
501 mut result := ''
502 #result = new string(s.str.repeat(count))
503
504 return result
505}
506
507// TODO(playX): Use this iterator instead of using .split('').map(c => u8(c))
508#function string_iterator(string) { this.stringIteratorFieldIndex = 0; this.stringIteratorIteratedString = string.str; }
509#string_iterator.prototype.next = function next() {
510#var done = true;
511#var value = undefined;
512#var position = this.stringIteratorFieldIndex;
513#if (position !== -1) {
514#var string = this.stringIteratorIteratedString;
515#var length = string.length >>> 0;
516#if (position >= length) {
517#this.stringIteratorFieldIndex = -1;
518#} else {
519#done = false;
520#var first = string.charCodeAt(position);
521#if (first < 0xD800 || first > 0xDBFF || position + 1 === length)
522#value = new u8(string[position]);
523#else {
524#value = new u8(string[position]+string[position+1])
525#}
526#this.stringIteratorFieldIndex = position + value.length;
527#}
528#}
529#return {
530#value, done
531#}
532#}
533#string.prototype[Symbol.iterator] = function () { return new string_iterator(this) }
534
535// TODO: Make these functions actually work.
536// strip_margin allows multi-line strings to be formatted in a way that removes white-space
537// before a delimiter. By default `|` is used.
538// Note: the delimiter has to be a byte at this time. That means surrounding
539// the value in ``.
540//
541// Example:
542// st := 'Hello there,
543// |this is a string,
544// | Everything before the first | is removed'.strip_margin()
545// Returns:
546// Hello there,
547// this is a string,
548// Everything before the first | is removed
549pub fn (s string) strip_margin() string {
550 return s.strip_margin_custom(`|`)
551}
552
553// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|`
554@[direct_array_access]
555pub fn (s string) strip_margin_custom(del u8) string {
556 mut sep := del
557 if sep.is_space() {
558 eprintln('Warning: `strip_margin` cannot use white-space as a delimiter')
559 eprintln(' Defaulting to `|`')
560 sep = `|`
561 }
562 // don't know how much space the resulting string will be, but the max it
563 // can be is this big
564 mut ret := []u8{}
565 #ret = new array()
566
567 mut count := 0
568 for i := 0; i < s.len; i++ {
569 if s[i] in [10, 13] {
570 unsafe {
571 ret[count] = s[i]
572 }
573 count++
574 // CRLF
575 if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 {
576 unsafe {
577 ret[count] = s[i + 1]
578 }
579 count++
580 i++
581 }
582 for s[i] != sep {
583 i++
584 if i >= s.len {
585 break
586 }
587 }
588 } else {
589 unsafe {
590 ret[count] = s[i]
591 }
592 count++
593 }
594 }
595 /*
596 unsafe {
597 ret[count] = 0
598 return ret.vstring_with_len(count)
599 }*/
600 mut result := ''
601 #for (let x of ret.arr) result.str += String.fromCharCode(x.val)
602
603 return result
604}
605
606// split_nth splits the string based on the passed `delim` substring.
607// It returns the first Nth parts. When N=0, return all the splits.
608// The last returned element has the remainder of the string, even if
609// the remainder contains more `delim` substrings.
610@[direct_array_access]
611pub fn (s string) split_nth(delim string, nth int) []string {
612 mut res := []string{}
613 mut i := 0
614
615 match delim.len {
616 0 {
617 i = 1
618 for ch in s {
619 if nth > 0 && i >= nth {
620 res << s[i..]
621 break
622 }
623 res << ch.str()
624 i++
625 }
626 return res
627 }
628 1 {
629 mut start := 0
630 delim_byte := delim[0]
631
632 for i < s.len {
633 if s[i] == delim_byte {
634 was_last := nth > 0 && res.len == nth - 1
635 if was_last {
636 break
637 }
638 val := s[start..i] //.substr(start, i)
639 res << val
640 start = i + delim.len
641 i = start
642 } else {
643 i++
644 }
645 }
646
647 // Then the remaining right part of the string
648 if nth < 1 || res.len < nth {
649 res << s[start..]
650 }
651 return res
652 }
653 else {
654 mut start := 0
655 // Take the left part for each delimiter occurrence
656 for i <= s.len {
657 is_delim := i + delim.len <= s.len && s[i..i + delim.len] == delim
658 if is_delim {
659 was_last := nth > 0 && res.len == nth - 1
660 if was_last {
661 break
662 }
663 val := s[start..i] // .substr(start, i)
664 res << val
665 start = i + delim.len
666 i = start
667 } else {
668 i++
669 }
670 }
671 // Then the remaining right part of the string
672 if nth < 1 || res.len < nth {
673 res << s[start..]
674 }
675 return res
676 }
677 }
678}
679
680struct RepIndex {
681 idx int
682 val_idx int
683}
684
685// replace_each replaces all occurrences of the string pairs given in `vals`.
686// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC'
687@[direct_array_access]
688pub fn (s string) replace_each(vals []string) string {
689 if s.len == 0 || vals.len == 0 {
690 return s.clone()
691 }
692
693 if vals.len % 2 != 0 {
694 eprintln('string.replace_each(): odd number of strings')
695 return s.clone()
696 }
697
698 // `rep` - string to replace
699 // `with_` - string to replace with_
700 // Remember positions of all rep strings, and calculate the length
701 // of the new string to do just one allocation.
702
703 mut idxs := []RepIndex{}
704 mut idx := 0
705 mut new_len := s.len
706 s_ := s.clone()
707 #function setCharAt(str,index,chr) {
708 #if(index > str.length-1) return str;
709 #return str.substring(0,index) + chr + str.substring(index+1);
710 #}
711
712 for rep_i := 0; rep_i < vals.len; rep_i = rep_i + 2 {
713 rep := vals[rep_i]
714
715 mut with_ := vals[rep_i + 1]
716 with_ = with_
717
718 for {
719 idx = s_.index_after_(rep, idx)
720 if idx == -1 {
721 break
722 }
723
724 for i in 0 .. rep.len {
725 mut j_ := i
726 j_ = j_
727 #s_.str = setCharAt(s_.str,idx + i, String.fromCharCode(127))
728 }
729
730 rep_idx := RepIndex{
731 idx: 0
732 val_idx: 0
733 }
734 // todo: primitives should always be copied
735 #rep_idx.idx = idx.val
736 #rep_idx.val_idx = new int(rep_i.val)
737 idxs << rep_idx
738 idx += rep.len
739 new_len += with_.len - rep.len
740 }
741 }
742
743 if idxs.len == 0 {
744 return s.clone()
745 }
746
747 idxs.sort(a.idx < b.idx)
748
749 mut b := ''
750 #for (let i = 0; i < new_len.val;i++) b.str += String.fromCharCode(127)
751
752 new_len = new_len
753 mut idx_pos := 0
754 mut cur_idx := idxs[idx_pos]
755 mut b_i := 0
756 for i := 0; i < s.len; i++ {
757 if i == cur_idx.idx {
758 rep := vals[cur_idx.val_idx]
759 with_ := vals[cur_idx.val_idx + 1]
760 for j in 0 .. with_.len {
761 mut j_ := j
762
763 j_ = j_
764 #b.str = setCharAt(b.str,b_i, with_.str[j])
765 //#b.str[b_i] = with_.str[j]
766 b_i++
767 }
768 i += rep.len - 1
769 idx_pos++
770 if idx_pos < idxs.len {
771 cur_idx = idxs[idx_pos]
772 }
773 } else {
774 #b.str = setCharAt(b.str,b_i,s.str[i]) //b.str[b_i] = s.str[i]
775 b_i++
776 }
777 }
778
779 return b
780}
781
782// format replaces positional placeholders like `{0}` and `{1}` in `s`
783// with the corresponding values from `args`.
784// Use `{{` and `}}` to output literal braces.
785@[direct_array_access]
786pub fn (s string) format(args ...string) string {
787 if s.len == 0 {
788 return ''
789 }
790 mut out := strings.new_builder(s.len)
791 mut i := 0
792 for i < s.len {
793 ch := s[i]
794 if ch == `{` {
795 if i + 1 < s.len && s[i + 1] == `{` {
796 out.write_byte(`{`)
797 i += 2
798 continue
799 }
800 mut j := i + 1
801 if j >= s.len || !s[j].is_digit() {
802 out.write_byte(ch)
803 i++
804 continue
805 }
806 mut idx := 0
807 mut overflowed := false
808 for j < s.len && s[j].is_digit() {
809 digit := int(s[j] - `0`)
810 if idx > (max_int - digit) / 10 {
811 overflowed = true
812 break
813 }
814 idx = idx * 10 + digit
815 j++
816 }
817 if !overflowed && j < s.len && s[j] == `}` {
818 if idx < args.len {
819 out.write_string(args[idx])
820 } else {
821 out.write_string(s[i..j + 1])
822 }
823 i = j + 1
824 continue
825 }
826 out.write_byte(ch)
827 i++
828 continue
829 }
830 if ch == `}` && i + 1 < s.len && s[i + 1] == `}` {
831 out.write_byte(`}`)
832 i += 2
833 continue
834 }
835 out.write_byte(ch)
836 i++
837 }
838 return out.str()
839}
840
841// last_index returns the position of the last occurrence of the input string.
842fn (s string) index_last_(p string) int {
843 if p.len > s.len || p.len == 0 {
844 return -1
845 }
846 mut i := s.len - p.len
847 for i >= 0 {
848 mut j := 0
849 for j < p.len && s[i + j] == p[j] {
850 j++
851 }
852 if j == p.len {
853 return i
854 }
855 i--
856 }
857 return -1
858}
859
860// last_index returns the position of the first character of the *last* occurrence of the `needle` string in `s`.
861@[inline]
862pub fn (s string) last_index(needle string) ?int {
863 idx := s.index_last_(needle)
864 if idx == -1 {
865 return none
866 }
867 return idx
868}
869
870// last_index_u8 returns the index of the last occurrence of byte `c` if it was found in the string.
871@[direct_array_access]
872pub fn (s string) last_index_u8(c u8) int {
873 for i := s.len - 1; i >= 0; i-- {
874 if s[i] == c {
875 return i
876 }
877 }
878 return -1
879}
880
881pub fn (s string) trim_space() string {
882 res := ''
883 #res.str = s.str.trim()
884
885 return res
886}
887
888pub fn (s string) index_after(p string, start int) ?int {
889 if p.len > s.len {
890 return none
891 }
892
893 mut strt := start
894 if start < 0 {
895 strt = 0
896 }
897 if start >= s.len {
898 return none
899 }
900 mut i := strt
901
902 for i < s.len {
903 mut j := 0
904 mut ii := i
905 for j < p.len && s[ii] == p[j] {
906 j++
907 ii++
908 }
909
910 if j == p.len {
911 return i
912 }
913 i++
914 }
915 return none
916}
917
918pub fn (s string) index_after_(p string, start int) int {
919 if p.len > s.len {
920 return -1
921 }
922
923 mut strt := start
924 if start < 0 {
925 strt = 0
926 }
927 if start >= s.len {
928 return -1
929 }
930 mut i := strt
931
932 for i < s.len {
933 mut j := 0
934 mut ii := i
935 for j < p.len && s[ii] == p[j] {
936 j++
937 ii++
938 }
939
940 if j == p.len {
941 return i
942 }
943 i++
944 }
945 return -1
946}
947
948pub fn (s string) split_into_lines() []string {
949 mut res := []string{}
950 if s.len == 0 {
951 return res
952 }
953 #res.arr.arr = s.str.split(/\r?\n|\r/)
954 #if (res.arr.arr[res.arr.arr.length-1] == "") res.arr.arr.pop();
955 #res.arr.len = new int(res.arr.arr.length);
956 #res.arr.cap = new int(res.arr.arr.length);
957
958 return res
959}
960
961// replace_once replaces the first occurrence of `rep` with the string passed in `with`.
962pub fn (s string) replace_once(rep string, with_ string) string {
963 s2 := ''
964 #s2.val = s.str.replace(rep.str,with_.str)
965
966 return s2
967}
968
969pub fn (s string) title() string {
970 words := s.split(' ')
971 mut tit := []string{}
972 for word in words {
973 tit << word.capitalize()
974 }
975
976 title := tit.join(' ')
977 return title
978}
979
980// index_any returns the position of any of the characters in the input string - if found.
981pub fn (s string) index_any(chars string) int {
982 for i, ss in s {
983 for c in chars {
984 if c == ss {
985 return i
986 }
987 }
988 }
989 return -1
990}
991
992// limit returns a portion of the string, starting at `0` and extending for a given number of characters afterward.
993// 'hello'.limit(2) => 'he'
994// 'hi'.limit(10) => 'hi'
995pub fn (s string) limit(max int) string {
996 u := s.runes()
997 if u.len <= max {
998 return s.clone()
999 }
1000 return u[0..max].string()
1001}
1002
1003// is_title returns true if all words of the string is capitalized.
1004// Example: assert 'Hello V Developer'.is_title() == true
1005pub fn (s string) is_title() bool {
1006 words := s.split(' ')
1007 for word in words {
1008 if !word.is_capital() {
1009 return false
1010 }
1011 }
1012 return true
1013}
1014
1015// is_capital returns `true`, if the first character in the string `s`,
1016// is a capital letter, and the rest are NOT.
1017// Example: assert 'Hello'.is_capital() == true
1018// Example: assert 'HelloWorld'.is_capital() == false
1019@[direct_array_access]
1020pub fn (s string) is_capital() bool {
1021 if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) {
1022 return false
1023 }
1024 for i in 1 .. s.len {
1025 if s[i] >= `A` && s[i] <= `Z` {
1026 return false
1027 }
1028 }
1029 return true
1030}
1031
1032// starts_with_capital returns `true`, if the first character in the string `s`,
1033// is a capital letter, even if the rest are not.
1034// Example: assert 'Hello'.starts_with_capital() == true
1035// Example: assert 'Hello. World.'.starts_with_capital() == true
1036@[direct_array_access]
1037pub fn (s string) starts_with_capital() bool {
1038 if s.len == 0 || !s[0].is_capital() {
1039 return false
1040 }
1041 return true
1042}
1043
1044// is_upper returns `true` if all characters in the string is uppercase.
1045// Example: assert 'HELLO V'.is_upper() == true
1046pub fn (s string) is_upper() bool {
1047 res := false
1048 #res.val = s.str == s.str.toUpperCase() && s.str != s.str.toLowerCase()
1049
1050 return res
1051}
1052
1053// is_upper returns `true` if all characters in the string is uppercase.
1054// Example: assert 'HELLO V'.is_upper() == true
1055pub fn (s string) is_lower() bool {
1056 res := false
1057 #res.val = s.str == s.str.toLowerCase() && s.str != s.str.toUpperCase()
1058
1059 return res
1060}
1061
1062pub fn (s string) reverse() string {
1063 res := ''
1064 #res.str = [...s.str].reverse().join('')
1065
1066 return res
1067}
1068
1069pub fn (s string) trim(cutset string) string {
1070 if s == '' || cutset == '' {
1071 return s.clone()
1072 }
1073 mut pos_left := 0
1074 mut pos_right := s.len - 1
1075 mut cs_match := true
1076 for pos_left <= s.len && pos_right >= -1 && cs_match {
1077 cs_match = false
1078 for cs in cutset {
1079 if s[pos_left] == cs {
1080 pos_left++
1081 cs_match = true
1082 break
1083 }
1084 }
1085 for cs in cutset {
1086 if s[pos_right] == cs {
1087 pos_right--
1088 cs_match = true
1089 break
1090 }
1091 }
1092 if pos_left > pos_right {
1093 return ''
1094 }
1095 }
1096 return s.substr(pos_left, pos_right + 1)
1097}
1098
1099pub fn (s []string) join(sep string) string {
1100 mut res := ''
1101 for i, str in s {
1102 res += str
1103 if i != s.len - 1 {
1104 res += sep
1105 }
1106 }
1107 return res
1108}
1109
1110// There's no better way to find length of JS String in bytes.
1111#Object.defineProperty(string.prototype,"len", { get: function() {return new int(new TextEncoder().encode(this.str).length);}, set: function(l) {/* ignore */ } });
1112// index returns the position of the first character of the input string.
1113// It will return `none` if the input string can't be found.
1114pub fn (s string) index(search string) ?int {
1115 res := 0
1116 #res.val = s.str.indexOf(search)
1117 if res == -1 {
1118 return none
1119 }
1120 return res
1121}
1122
1123pub fn (_rune string) utf32_code() int {
1124 res := 0
1125 #res.val = s.str.charCodeAt()
1126
1127 return res
1128}
1129
1130// tos converts a JS string or an addressable byte range into a V string.
1131pub fn tos(source TosSource, lens ...int) string {
1132 mut res := ''
1133 #const source_value = source instanceof $ref ? source.valueOf() : source
1134 if lens.len == 0 {
1135 #if (source_value instanceof string) {
1136 #res.str = source_value.str
1137 #} else if (typeof source_value === 'string' || source_value instanceof String) {
1138 #res.str = source_value.toString()
1139 #} else {
1140 panic('tos(): unsupported source in JS backend')
1141 #}
1142
1143 return res
1144 }
1145 if lens.len != 1 {
1146 panic('tos(): expected exactly one length argument')
1147 }
1148 len := lens[0]
1149 if len < 0 {
1150 panic('tos(): negative length')
1151 }
1152 #if (source_value === null || source_value === undefined) {
1153 panic('tos(): nil string')
1154 #}
1155 #if (source && source._v_array !== undefined && source._v_index !== undefined) {
1156 #const start = source._v_index.valueOf()
1157 #for (let i = 0; i < len.valueOf(); ++i) res.str += String.fromCharCode(source._v_array.arr.get(new int(start + i)).valueOf())
1158 #} else if (source_value.arr !== undefined && typeof source_value.arr.get === 'function') {
1159 #for (let i = 0; i < len.valueOf(); ++i) res.str += String.fromCharCode(source_value.arr.get(new int(i)).valueOf())
1160 #} else if (source_value instanceof string) {
1161 #res.str = source_value.str.slice(0, len.valueOf())
1162 #} else if (typeof source_value === 'string' || source_value instanceof String) {
1163 #res.str = source_value.toString().slice(0, len.valueOf())
1164 #} else {
1165 panic('tos(): unsupported source in JS backend')
1166 #}
1167
1168 return res
1169}
1170
1171pub fn (s string) compare(a string) int {
1172 min_len := if s.len < a.len { s.len } else { a.len }
1173 for i in 0 .. min_len {
1174 if s[i] < a[i] {
1175 return -1
1176 }
1177 if s[i] > a[i] {
1178 return 1
1179 }
1180 }
1181
1182 if s.len < a.len {
1183 return -1
1184 }
1185 if s.len > a.len {
1186 return 1
1187 }
1188 return 0
1189}
1190