diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 05b2a82ca4..8c34829ddd 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -74,7 +74,7 @@ fn (a &array) set_len(i int) { #a.arr.length=i } -pub fn (mut a array) sort_with_comparator(compare voidptr) { +pub fn (mut a array) sort_with_compare(compare voidptr) { #a.arr.sort(compare) } diff --git a/vlib/builtin/js/byte.js.v b/vlib/builtin/js/byte.js.v new file mode 100644 index 0000000000..a2508db399 --- /dev/null +++ b/vlib/builtin/js/byte.js.v @@ -0,0 +1,8 @@ +module builtin + +pub fn (b byte) is_space() bool { + mut result := false + #result = /^\s*$/.test(String.fromCharCode(b)) + + return result +} diff --git a/vlib/builtin/js/jsfns.js.v b/vlib/builtin/js/jsfns.js.v index 79f46261f7..277c702482 100644 --- a/vlib/builtin/js/jsfns.js.v +++ b/vlib/builtin/js/jsfns.js.v @@ -121,5 +121,5 @@ fn (s JS.String) toUpperCase() JS.String fn (s JS.String) toLowerCase() JS.String fn (s JS.String) concat(a JS.String) JS.String fn (s JS.String) includes(substr JS.String) bool -fn (s JS.String) ends_with(substr JS.String) bool -fn (s JS.String) starts_with(substr JS.String) bool +fn (s JS.String) endsWith(substr JS.String) bool +fn (s JS.String) startsWith(substr JS.String) bool diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index 20387a823a..4fb264c95a 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -94,15 +94,42 @@ pub fn (s string) count(substr string) int { } pub fn (s string) ends_with(p string) bool { - return s.str.ends_with(p.str) + return s.str.endsWith(p.str) } pub fn (s string) starts_with(p string) bool { - return s.str.starts_with(p.str) + return s.str.startsWith(p.str) } pub fn (s string) fields() []string { - return [] // s.str.split() + mut res := []string{} + mut word_start := 0 + mut word_len := 0 + mut is_in_word := false + mut is_space := false + for i, c in s { + is_space = c in [32, 9, 10] + if !is_space { + word_len++ + } + if !is_in_word && !is_space { + word_start = i + is_in_word = true + continue + } + if is_space && is_in_word { + res << s[word_start..word_start + word_len] + is_in_word = false + word_len = 0 + word_start = 0 + continue + } + } + if is_in_word && word_len > 0 { + // collect the remainder word at the end + res << s[word_start..s.len] + } + return res } pub fn (s string) find_between(start string, end string) string { @@ -167,3 +194,265 @@ pub fn (s string) u32() u32 { pub fn (s string) u64() u64 { return u64(JS.parseInt(s)) } + +// trim_right strips any of the characters given in `cutset` from the right of the string. +// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V' +pub fn (s string) trim_right(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + + mut pos := s.len - 1 + + for pos >= 0 { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + } + } + if !found { + break + } + pos-- + } + + if pos < 0 { + return '' + } + + return s[..pos + 1] +} + +// trim_left strips any of the characters given in `cutset` from the left of the string. +// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer' +[direct_array_access] +pub fn (s string) trim_left(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + mut pos := 0 + for pos < s.len { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + break + } + } + if !found { + break + } + pos++ + } + return s[pos..] +} + +// trim_prefix strips `str` from the start of the string. +// Example: assert 'WorldHello V'.trim_prefix('World') == 'Hello V' +pub fn (s string) trim_prefix(str string) string { + if s.starts_with(str) { + return s[str.len..] + } + return s.clone() +} + +// trim_suffix strips `str` from the end of the string. +// Example: assert 'Hello VWorld'.trim_suffix('World') == 'Hello V' +pub fn (s string) trim_suffix(str string) string { + if s.ends_with(str) { + return s[..s.len - str.len] + } + return s.clone() +} + +// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`. +pub fn compare_strings(a &string, b &string) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +// compare_strings_reverse returns `1` if `a < b`, `-1` if `a > b` else `0`. +fn compare_strings_reverse(a &string, b &string) int { + if a < b { + return 1 + } + if a > b { + return -1 + } + return 0 +} + +// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`. +fn compare_strings_by_len(a &string, b &string) int { + if a.len < b.len { + return -1 + } + if a.len > b.len { + return 1 + } + return 0 +} + +// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing. +fn compare_lower_strings(a &string, b &string) int { + aa := a.to_lower() + bb := b.to_lower() + return compare_strings(&aa, &bb) +} + +// at returns the byte at index `idx`. +// Example: assert 'ABC'.at(1) == byte(`B`) +fn (s string) at(idx int) byte { + mut result := byte(0) + #result = new byte(s.str.charCodeAt(result)) + + return result +} + +pub fn (s string) to_lower() string { + mut result := '' + #let str = s.str.toLowerCase() + #result = new string(str) + + return result +} + +pub fn (s string) to_upper() string { + mut result := '' + #let str = s.str.toUpperCase() + #result = new string(str) + + return result +} + +// sort sorts the string array. +pub fn (mut s []string) sort() { + s.sort_with_compare(compare_strings) +} + +// sort_ignore_case sorts the string array using case insesitive comparing. +pub fn (mut s []string) sort_ignore_case() { + s.sort_with_compare(compare_lower_strings) +} + +// sort_by_len sorts the the string array by each string's `.len` length. +pub fn (mut s []string) sort_by_len() { + s.sort_with_compare(compare_strings_by_len) +} + +// str returns a copy of the string +pub fn (s string) str() string { + return s.clone() +} + +pub fn (s string) repeat(count int) string { + mut result := '' + #result = new string(s.str.repeat(count)) + + return result +} + +// TODO(playX): Use this iterator instead of using .split('').map(c => byte(c)) +#function string_iterator(string) { this.stringIteratorFieldIndex = 0; this.stringIteratorIteratedString = string.str; } +#string_iterator.prototype.next = function next() { +#var done = true; +#var value = undefined; +#var position = this.stringIteratorFieldIndex; +#if (position !== -1) { +#var string = this.stringIteratorIteratedString; +#var length = string.length >>> 0; +#if (position >= length) { +#this.stringIteratorFieldIndex = -1; +#} else { +#done = false; +#var first = string.charCodeAt(position); +#if (first < 0xD800 || first > 0xDBFF || position + 1 === length) +#value = new byte(string[position]); +#else { +#value = new byte(string[position]+string[position+1]) +#} +#this.stringIteratorFieldIndex = position + value.length; +#} +#} +#return { +#value, done +#} +#} +#string.prototype[Symbol.iterator] = function () { return new string_iterator(this) } + +// TODO: Make these functions actually work. +// strip_margin allows multi-line strings to be formatted in a way that removes white-space +// before a delimeter. by default `|` is used. +// Note: the delimiter has to be a byte at this time. That means surrounding +// the value in ``. +// +// Example: +// st := 'Hello there, +// |this is a string, +// | Everything before the first | is removed'.strip_margin() +// Returns: +// Hello there, +// this is a string, +// Everything before the first | is removed +pub fn (s string) strip_margin() string { + return s.strip_margin_custom(`|`) +} + +// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|` +[direct_array_access] +pub fn (s string) strip_margin_custom(del byte) string { + mut sep := del + if sep.is_space() { + eprintln('Warning: `strip_margin` cannot use white-space as a delimiter') + eprintln(' Defaulting to `|`') + sep = `|` + } + // don't know how much space the resulting string will be, but the max it + // can be is this big + mut ret := []byte{} + #ret = new array() + + mut count := 0 + for i := 0; i < s.len; i++ { + if s[i] in [10, 13] { + unsafe { + ret[count] = s[i] + } + count++ + // CRLF + if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 { + unsafe { + ret[count] = s[i + 1] + } + count++ + i++ + } + for s[i] != sep { + i++ + if i >= s.len { + break + } + } + } else { + unsafe { + ret[count] = s[i] + } + count++ + } + } + /* + unsafe { + ret[count] = 0 + return ret.vstring_with_len(count) + }*/ + mut result := '' + #for (let x of ret.arr) result.str += String.fromCharCode(x.val) + + return result +} diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index f1c2e3b007..af9de52afe 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -359,6 +359,11 @@ pub fn (typ Type) is_string() bool { return typ.idx() in ast.string_type_idxs } +[inline] +pub fn (typ Type) is_bool() bool { + return typ.idx() == ast.bool_type_idx +} + pub const ( void_type_idx = 1 voidptr_type_idx = 2 diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 9b84abd1f9..26ac15d5d1 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -308,7 +308,7 @@ fn (mut g JsGen) gen_builtin_type_defs() { default_value: 'new Number(0)' constructor: 'this.val = typeof(val) == "string" ? val.charCodeAt() : (val | 0)' value_of: 'this.val | 0' - to_string: 'String.fromCharCode(this.val)' + to_string: 'new string(this.val + "")' eq: 'this.valueOf() === other.valueOf()' ) } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index a4617af1a3..503945634a 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -674,6 +674,7 @@ fn (mut g JsGen) expr(node ast.Expr) { } else { g.write(node.op.str()) g.expr(node.right) + g.write('.valueOf()') } } ast.RangeExpr { @@ -1454,6 +1455,7 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { } if i < node.branches.len - 1 || !node.has_else { g.expr(branch.cond) + g.write('.valueOf()') g.write(' ? ') } g.stmts(branch.stmts) @@ -1474,6 +1476,7 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { g.write('true') } else { g.expr(branch.cond) + g.write('.valueOf()') } g.writeln(') {') } @@ -1481,6 +1484,7 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { } else if i < node.branches.len - 1 || !node.has_else { g.write('} else if (') g.expr(branch.cond) + g.write('.valueOf()') g.writeln(') {') } else if i == node.branches.len - 1 && node.has_else { /* @@ -1540,10 +1544,12 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { // TODO: What's the best way to do this? // 'string'[3] = `o` } else { + // TODO: Maybe use u16 there? JS String returns values up to 2^16-1 + g.write('new byte(') g.expr(expr.left) g.write('.str.charCodeAt(') g.expr(expr.index) - g.write(')') + g.write('))') } } else { // TODO Does this cover all cases? @@ -1768,14 +1774,14 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { text := it.val.replace("'", "\\'") should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) - if should_cast { + if true || should_cast { if g.file.mod.name == 'builtin' { g.write('new ') } g.write('string(') } g.write("'$text'") - if should_cast { + if true || should_cast { g.write(')') } } diff --git a/vlib/v/gen/js/tests/testdata/byte_is_space.out b/vlib/v/gen/js/tests/testdata/byte_is_space.out new file mode 100644 index 0000000000..d25232800f --- /dev/null +++ b/vlib/v/gen/js/tests/testdata/byte_is_space.out @@ -0,0 +1,2 @@ +true +false \ No newline at end of file diff --git a/vlib/v/gen/js/tests/testdata/byte_is_space.v b/vlib/v/gen/js/tests/testdata/byte_is_space.v new file mode 100644 index 0000000000..8d99196bc7 --- /dev/null +++ b/vlib/v/gen/js/tests/testdata/byte_is_space.v @@ -0,0 +1,4 @@ +x := ' x' + +println(x[0].is_space()) +println(x[1].is_space()) diff --git a/vlib/v/gen/js/tests/testdata/compare_ints.out b/vlib/v/gen/js/tests/testdata/compare_ints.out index 9f204ea56e..2db62acc96 100644 --- a/vlib/v/gen/js/tests/testdata/compare_ints.out +++ b/vlib/v/gen/js/tests/testdata/compare_ints.out @@ -1 +1 @@ -2 > 1 +2 > 1 \ No newline at end of file diff --git a/vlib/v/gen/js/tests/testdata/hw.out b/vlib/v/gen/js/tests/testdata/hw.out index 3b18e512db..95d09f2b10 100644 --- a/vlib/v/gen/js/tests/testdata/hw.out +++ b/vlib/v/gen/js/tests/testdata/hw.out @@ -1 +1 @@ -hello world +hello world \ No newline at end of file diff --git a/vlib/v/gen/js/tests/testdata/string_methods.out b/vlib/v/gen/js/tests/testdata/string_methods.out new file mode 100644 index 0000000000..de2a355c44 --- /dev/null +++ b/vlib/v/gen/js/tests/testdata/string_methods.out @@ -0,0 +1,3 @@ +Hello V developer + Hello V +Hello V \ No newline at end of file diff --git a/vlib/v/gen/js/tests/testdata/string_methods.v b/vlib/v/gen/js/tests/testdata/string_methods.v new file mode 100644 index 0000000000..1594b4882a --- /dev/null +++ b/vlib/v/gen/js/tests/testdata/string_methods.v @@ -0,0 +1,3 @@ +println('d Hello V developer'.trim_left(' d')) +println(' Hello V d'.trim_right(' d')) +println('WorldHello V'.trim_prefix('World'))