From 8d1ba52d0cb06966d1a6216a70faff792bbd1551 Mon Sep 17 00:00:00 2001 From: playX Date: Mon, 4 Oct 2021 18:28:30 +0300 Subject: [PATCH] js: fix string.bytes codegen, readline, add tests for `strings` (#12060) --- vlib/builtin/js/array.js.v | 14 +- vlib/builtin/js/string.js.v | 22 ++- vlib/readline/readline_js.js.v | 32 ++++ vlib/readline/readline_test.v | 2 +- vlib/strings/builder.js.v | 50 +++++- vlib/strings/builder_test.js.v | 94 +++++++++++ vlib/strings/similarity_test.js.v | 13 ++ vlib/strings/strings.js.v | 8 +- vlib/strings/strings_test.js.v | 14 ++ .../strings/textscanner/textscanner_test.js.v | 159 ++++++++++++++++++ vlib/v/gen/js/builtin_types.v | 8 + vlib/v/gen/js/fn.v | 2 +- vlib/v/gen/js/infix.v | 3 +- vlib/v/gen/js/js.v | 8 +- 14 files changed, 399 insertions(+), 30 deletions(-) create mode 100644 vlib/readline/readline_js.js.v create mode 100644 vlib/strings/builder_test.js.v create mode 100644 vlib/strings/similarity_test.js.v create mode 100644 vlib/strings/strings_test.js.v create mode 100644 vlib/strings/textscanner/textscanner_test.js.v diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 834ae617d..5e16df28f 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -45,9 +45,9 @@ fn (mut a array_buffer) set(ix int, val voidptr) { #array_buffer.prototype.set = function(ix,val) { array_buffer_set(this,ix,val); } struct array { -mut: +pub mut: arr array_buffer -pub: + len int cap int } @@ -67,6 +67,14 @@ fn v_sort(mut arr array, comparator fn (voidptr, voidptr) int) { } } +// trim trims the array length to "index" without modifying the allocated data. If "index" is greater +// than len nothing will be changed. +pub fn (mut a array) trim(index int) { + if index < a.len { + a.len = index + } +} + #function flatIntoArray(target, source, sourceLength, targetIndex, depth) { #"use strict"; # @@ -368,7 +376,7 @@ pub fn (a &array) free() { // todo: once (a []byte) will work rewrite this pub fn (a array) bytestr() string { res := '' - #a.arr.arr.forEach((item) => res.str += String.fromCharCode(+item)) + #for (let i = 0;i < a.arr.len.valueOf();i++) res.str += String.fromCharCode(a.arr.get(new int(i))) return res } diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index 1420f5cfe..b48e92051 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -71,7 +71,7 @@ pub fn (s string) split(dot string) []string { pub fn (s string) bytes() []byte { sep := '' mut arr := s.str.split(sep.str).map(it.charCodeAt(0)) - #arr = new array(arr) + #arr = new array(new array_buffer({arr,index_start: new int(0),len: new int(arr.length)})) return arr } @@ -735,11 +735,21 @@ pub fn (s string) index_after(p string, start int) int { pub fn (s string) split_into_lines() []string { mut res := []string{} - #let i = 0 - #s.str.split('\n').forEach((str) => { - #res.arr[i] = new string(str); - #}) - + if s.len == 0 { + return res + } + mut start := 0 + mut end := 0 + for i := 0; i < s.len; i++ { + if s[i] == 10 { + end = if i > 0 && s[i - 1] == 13 { i - 1 } else { i } + res << if start == end { '' } else { s[start..end] } + start = i + 1 + } + } + if start < s.len { + res << s[start..] + } return res } diff --git a/vlib/readline/readline_js.js.v b/vlib/readline/readline_js.js.v new file mode 100644 index 000000000..22a3350c6 --- /dev/null +++ b/vlib/readline/readline_js.js.v @@ -0,0 +1,32 @@ +module readline + +#const $readline = require('readline') + +struct Termios {} + +// Only use standard os.get_line +// Need implementation for readline capabilities +// +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. + +pub fn (mut r Readline) read_line(prompt string) ?string { + res := '' + print(prompt) + #const rl = $readline.createInterface({input: $process.stdin,output: $process.stdout,prompt: prompt.str}) + #rl.prompt() + #rl.on('line', function (ans) { rl.prompt(); res.str = ans; rl.close();}) + + return res +} + +pub fn read_line(prompt string) ?string { + mut r := Readline{} + s := r.read_line(prompt) ? + return s +} diff --git a/vlib/readline/readline_test.v b/vlib/readline/readline_test.v index a3613e85a..fc43a83a4 100644 --- a/vlib/readline/readline_test.v +++ b/vlib/readline/readline_test.v @@ -17,4 +17,4 @@ fn test_struct_readline() { // eprintln('methods: $methods') assert 'read_line_utf8' in methods assert 'read_line' in methods -} +} \ No newline at end of file diff --git a/vlib/strings/builder.js.v b/vlib/strings/builder.js.v index febf3a80a..60efca172 100644 --- a/vlib/strings/builder.js.v +++ b/vlib/strings/builder.js.v @@ -3,32 +3,37 @@ // that can be found in the LICENSE file. module strings +/* pub struct Builder { mut: buf []byte pub mut: len int initial_size int = 1 -} +}*/ + +pub type Builder = []byte pub fn new_builder(initial_size int) Builder { - return Builder{[]byte{cap: initial_size}, 0, initial_size} + return []byte{cap: initial_size} } pub fn (mut b Builder) write_b(data byte) { - b.buf << data + b << data } pub fn (mut b Builder) write(data []byte) ?int { if data.len == 0 { return 0 } - b.buf << data + b << data return data.len } pub fn (b &Builder) byte_at(n int) byte { - return b.buf[n] + unsafe { + return b[n] + } } pub fn (mut b Builder) write_string(s string) { @@ -37,7 +42,7 @@ pub fn (mut b Builder) write_string(s string) { } for c in s { - b.buf << c + b << c } } @@ -46,14 +51,41 @@ pub fn (mut b Builder) writeln(s string) { b.write_string(s) } - b.buf << 10 + b << 10 } pub fn (mut b Builder) str() string { s := '' - #for (const c of b.val.buf.arr.arr) + #for (const c of b.val.arr.arr) #s.str += String.fromCharCode(+c) - + b.trim(0) return s } + +pub fn (mut b Builder) cut_last(n int) string { + cut_pos := b.len - n + x := b[cut_pos..] + res := x.bytestr() + b.trim(cut_pos) + return res +} + +pub fn (mut b Builder) go_back_to(pos int) { + b.trim(pos) +} + +// go_back discards the last `n` bytes from the buffer +pub fn (mut b Builder) go_back(n int) { + b.trim(b.len - n) +} + +// cut_to cuts the string after `pos` and returns it. +// if `pos` is superior to builder length, returns an empty string +// and cancel further operations +pub fn (mut b Builder) cut_to(pos int) string { + if pos > b.len { + return '' + } + return b.cut_last(b.len - pos) +} diff --git a/vlib/strings/builder_test.js.v b/vlib/strings/builder_test.js.v new file mode 100644 index 000000000..e67a0f1bb --- /dev/null +++ b/vlib/strings/builder_test.js.v @@ -0,0 +1,94 @@ +import strings + +type MyInt = int + +const maxn = 100000 + +fn test_sb() { + mut sb := strings.new_builder(100) + sb.write_string('hi') + sb.write_string('!') + sb.write_string('hello') + assert sb.len == 8 + sb_end := sb.str() + assert sb_end == 'hi!hello' + assert sb.len == 0 + /// + sb = strings.new_builder(10) + sb.write_string('a') + sb.write_string('b') + assert sb.len == 2 + assert sb.str() == 'ab' + // Test interpolation optimization + sb = strings.new_builder(10) + x := 10 + y := MyInt(20) + sb.writeln('x = $x y = $y') + res := sb.str() + assert res[res.len - 1] == `\n` + println('"$res"') + assert res.trim_space() == 'x = 10 y = 20' + // + sb = strings.new_builder(10) + sb.write_string('x = $x y = $y') + assert sb.str() == 'x = 10 y = 20' + //$if !windows { + sb = strings.new_builder(10) + sb.write_string('123456') + last_2 := sb.cut_last(2) + assert last_2 == '56' + final_sb := sb.str() + assert final_sb == '1234' + //} +} + +fn test_big_sb() { + mut sb := strings.new_builder(100) + mut sb2 := strings.new_builder(10000) + for i in 0 .. maxn { + sb.writeln(i.str()) + sb2.write_string('+') + } + s := sb.str() + lines := s.split_into_lines() + assert lines.len == maxn + assert lines[0] == '0' + assert lines[1] == '1' + assert lines[777] == '777' + assert lines[98765] == '98765' + println(sb2.len) + assert sb2.len == maxn +} + +fn test_byte_write() { + mut sb := strings.new_builder(100) + temp_str := 'byte testing' + mut count := 0 + for word in temp_str { + sb.write_b(word) + count++ + assert count == sb.len + } + sb_final := sb.str() + assert sb_final == temp_str +} + +fn test_strings_builder_reuse() { + mut sb := strings.new_builder(256) + sb.write_string('world') + assert sb.str() == 'world' + sb.write_string('hello') + assert sb.str() == 'hello' +} + +fn test_cut_to() { + mut sb := strings.new_builder(16) + sb.write_string('hello') + assert sb.cut_to(3) == 'lo' + assert sb.len == 3 + assert sb.cut_to(3) == '' + assert sb.len == 3 + assert sb.cut_to(0) == 'hel' + assert sb.cut_to(32) == '' + assert sb.len == 0 +} diff --git a/vlib/strings/similarity_test.js.v b/vlib/strings/similarity_test.js.v new file mode 100644 index 000000000..965da450c --- /dev/null +++ b/vlib/strings/similarity_test.js.v @@ -0,0 +1,13 @@ +import strings + +fn test_levenshtein_distance() { + assert strings.levenshtein_distance('', '') == 0 + assert strings.levenshtein_distance('one', 'one') == 0 + assert strings.levenshtein_distance('', 'two') == 3 + assert strings.levenshtein_distance('three', '') == 5 + assert strings.levenshtein_distance('bananna', '') == 7 + assert strings.levenshtein_distance('cats', 'hats') == 1 + assert strings.levenshtein_distance('hugs', 'shrugs') == 2 + assert strings.levenshtein_distance('broom', 'shroom') == 2 + assert strings.levenshtein_distance('flomax', 'volmax') == 3 +} diff --git a/vlib/strings/strings.js.v b/vlib/strings/strings.js.v index 5bdff8cbb..2c3fe9657 100644 --- a/vlib/strings/strings.js.v +++ b/vlib/strings/strings.js.v @@ -9,10 +9,8 @@ pub fn repeat(c byte, n int) string { } pub fn repeat_string(s string, n int) string { - return '' - /* - // TODO: uncomment this. It is commented for now, so that `v doc strings` works - res := # s.repeat(n) + res := '' + #res.str = s.str.repeat(n.valueOf()) + return res - */ } diff --git a/vlib/strings/strings_test.js.v b/vlib/strings/strings_test.js.v new file mode 100644 index 000000000..ff5ddf504 --- /dev/null +++ b/vlib/strings/strings_test.js.v @@ -0,0 +1,14 @@ +import strings + +fn test_repeat() { + assert strings.repeat(`x`, 10) == 'xxxxxxxxxx' + assert strings.repeat(`a`, 1) == 'a' + assert strings.repeat(`a`, 0) == '' +} + +fn test_repeat_string() { + assert strings.repeat_string('abc', 3) == 'abcabcabc' + assert strings.repeat_string('abc', 1) == 'abc' + assert strings.repeat_string('abc', 0) == '' + assert strings.repeat_string('', 200) == '' +} diff --git a/vlib/strings/textscanner/textscanner_test.js.v b/vlib/strings/textscanner/textscanner_test.js.v new file mode 100644 index 000000000..e9d248769 --- /dev/null +++ b/vlib/strings/textscanner/textscanner_test.js.v @@ -0,0 +1,159 @@ +import strings.textscanner + +fn test_remaining() { + mut s := textscanner.new('abc') + assert s.remaining() == 3 + s.next() + s.next() + assert s.remaining() == 1 + s.next() + assert s.remaining() == 0 + s.next() + s.next() + assert s.remaining() == 0 + s.reset() + assert s.remaining() == 3 +} + +fn test_next() { + mut s := textscanner.new('abc') + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 + assert s.next() == -1 + assert s.next() == -1 +} + +fn test_skip() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.skip() + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_skip_n() { + mut s := textscanner.new('abc') + s.skip_n(2) + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_peek() { + mut s := textscanner.new('abc') + assert s.peek() == `a` + assert s.peek() == `a` + assert s.peek() == `a` + // + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_peek_n() { + mut s := textscanner.new('abc') + assert s.peek_n(0) == `a` + assert s.peek_n(1) == `b` + assert s.peek_n(2) == `c` + assert s.peek_n(3) == -1 + assert s.peek_n(4) == -1 + // + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_back() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.back() + assert s.next() == `a` + assert s.next() == `b` + s.back() + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_back_n() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.back_n(10) + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + s.back_n(2) + assert s.next() == `b` +} + +fn test_peek_back() { + mut s := textscanner.new('abc') + assert s.next() == `a` + assert s.next() == `b` + // check that calling .peek_back() multiple times + // does not change the state: + assert s.peek_back() == `a` + assert s.peek_back() == `a` + assert s.peek_back() == `a` + // advance, then peek_back again: + assert s.next() == `c` + assert s.peek_back() == `b` + // peeking before the start: + s.reset() + assert s.peek_back() == -1 + // peeking right at the end: + s.goto_end() + assert s.peek_back() == `b` +} + +fn test_peek_back_n() { + mut s := textscanner.new('abc') + s.goto_end() + assert s.peek_back_n(0) == `c` + assert s.peek_back_n(1) == `b` + assert s.peek_back_n(2) == `a` + assert s.peek_back_n(3) == -1 + assert s.peek_back_n(4) == -1 +} + +fn test_reset() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.next() + s.next() + assert s.next() == -1 + s.reset() + assert s.next() == `a` +} + +fn test_current() { + mut s := textscanner.new('abc') + assert s.current() == -1 + assert s.next() == `a` + assert s.current() == `a` + assert s.current() == `a` + assert s.peek_back() == -1 + assert s.next() == `b` + assert s.current() == `b` + assert s.current() == `b` + assert s.peek_back() == `a` + assert s.next() == `c` + assert s.current() == `c` + assert s.next() == -1 + assert s.current() == `c` + assert s.next() == -1 + assert s.current() == `c` + s.reset() + assert s.current() == -1 + assert s.next() == `a` + assert s.current() == `a` +} + +fn test_goto_end() { + mut s := textscanner.new('abc') + s.goto_end() + assert s.current() == `c` +} diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 7dd447657..cfb7d6bd5 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -111,10 +111,18 @@ fn (mut g JsGen) sym_to_js_typ(sym ast.TypeSymbol) string { return styp } +/* pub fn (mut g JsGen) base_type(t ast.Type) string { mut styp := g.cc_type(t, true) return styp } +*/ +fn (mut g JsGen) base_type(_t ast.Type) string { + t := g.unwrap_generic(_t) + share := t.share() + mut styp := if share == .atomic_t { t.atomic_typename() } else { g.cc_type(t, true) } + return styp +} pub fn (mut g JsGen) typ(t ast.Type) string { sym := g.table.get_final_type_symbol(t) diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index 6e68b881a..dd52f292e 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -106,7 +106,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) { g.inc_indent() g.writeln('try {') g.inc_indent() - g.write('return builtin.unwrap(') + g.write('return unwrap(') } if node.name == 'str' { mut rec_type := node.receiver_type diff --git a/vlib/v/gen/js/infix.v b/vlib/v/gen/js/infix.v index e6d55fc2f..0d34e69d4 100644 --- a/vlib/v/gen/js/infix.v +++ b/vlib/v/gen/js/infix.v @@ -8,7 +8,8 @@ fn (mut g JsGen) gen_plain_infix_expr(node ast.InfixExpr) { l_sym := g.table.get_final_type_symbol(it.left_type) r_sym := g.table.get_final_type_symbol(it.right_type) greater_typ := g.greater_typ(it.left_type, it.right_type) - g.write('new ${g.typ(greater_typ)}( ') + cast_ty := if greater_typ == it.left_type { l_sym.cname } else { r_sym.cname } + g.write('new ${g.js_name(cast_ty)}( ') g.cast_stack << greater_typ if (l_sym.kind == .i64 || l_sym.kind == .u64) || (r_sym.kind == .i64 || r_sym.kind == .u64) { g.write('BigInt(') diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index c87eb5e23..06276bc5e 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1398,7 +1398,7 @@ fn (mut g JsGen) gen_expr_stmt_no_semi(it ast.ExprStmt) { // cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo) fn (mut g JsGen) cc_type(typ ast.Type, is_prefix_struct bool) string { - sym := g.table.get_final_type_symbol(g.unwrap_generic(typ)) + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) mut styp := sym.cname match mut sym.info { ast.Struct, ast.Interface, ast.SumType { @@ -2306,10 +2306,10 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { left_typ := g.table.get_type_symbol(expr.left_type) // TODO: Handle splice setting if it's implemented if expr.index is ast.RangeExpr { - if left_typ.kind == .array { - g.write('array_slice(') - } else { + if left_typ.kind == .string { g.write('string_slice(') + } else { + g.write('array_slice(') } g.expr(expr.left) if expr.left_type.is_ptr() { -- 2.30.2