From f489c899876789d8f8660c110d1dd6dbe47c04a9 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 26 Mar 2020 20:17:14 +0200 Subject: [PATCH] v2: more informative assert output; string interpolation formatting --- vlib/builtin/builtin_nix.v | 2 +- vlib/builtin/string_strip_margin_test.v | 116 ++++++++++++++++ vlib/builtin/string_test.v | 131 ------------------ .../tests/string_interpolation_test.v | 71 ++++++---- .../tests/string_struct_interpolation_test.v | 25 ++++ vlib/v/ast/str.v | 3 + vlib/v/gen/cgen.v | 94 ++++++++++--- vlib/v/gen/cheaders.v | 2 - 8 files changed, 263 insertions(+), 181 deletions(-) create mode 100644 vlib/builtin/string_strip_margin_test.v create mode 100644 vlib/compiler/tests/string_struct_interpolation_test.v diff --git a/vlib/builtin/builtin_nix.v b/vlib/builtin/builtin_nix.v index 42dd54b970..afd1d61612 100644 --- a/vlib/builtin/builtin_nix.v +++ b/vlib/builtin/builtin_nix.v @@ -99,7 +99,7 @@ fn print_backtrace_skipping_top_frames_linux(skipframes int) bool { //////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames) csymbols := backtrace_symbols(&buffer[skipframes], nr_actual_frames) for i in 0 .. nr_actual_frames { - sframes << tos2(csymbols[i]) + sframes << tos2( byteptr( voidptr(csymbols[i]) ) ) } for sframe in sframes { executable := sframe.all_before('(') diff --git a/vlib/builtin/string_strip_margin_test.v b/vlib/builtin/string_strip_margin_test.v new file mode 100644 index 0000000000..5d532e7c2d --- /dev/null +++ b/vlib/builtin/string_strip_margin_test.v @@ -0,0 +1,116 @@ +// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +fn test_strip_margins_no_tabs() { + no_tabs := ['Hello there', + 'This is a string', + 'With multiple lines', + ].join('\n') + no_tabs_stripped := 'Hello there + |This is a string + |With multiple lines'.strip_margin() + assert no_tabs == no_tabs_stripped +} + +fn test_strip_margins_text_before() { + text_before := ['There is text', + 'before the delimiter', + 'that should be removed as well', + ].join('\n') + text_before_stripped := 'There is text + f lasj asldfj j lksjdf |before the delimiter + Which is removed hello |that should be removed as well'.strip_margin() + assert text_before_stripped == text_before +} + +fn test_strip_margins_white_space_after_delim() { + tabs := [' Tab', + ' spaces', + ' another tab', + ].join('\n') + tabs_stripped := ' Tab + | spaces + | another tab'.strip_margin() + assert tabs == tabs_stripped +} + +fn test_strip_margins_alternate_delim() { + alternate_delimiter := ['This has a different delim,', + 'but that is ok', + 'because everything works', + ].join('\n') + alternate_delimiter_stripped := 'This has a different delim, + #but that is ok + #because everything works'.strip_margin(`#`) + assert alternate_delimiter_stripped == alternate_delimiter +} + +fn test_strip_margins_multiple_delims_after_first() { + delim_after_first_instance := ['The delimiter used', + 'only matters the |||| First time it is seen', + 'not any | other | times', + ].join('\n') + delim_after_first_instance_stripped := 'The delimiter used + |only matters the |||| First time it is seen + |not any | other | times'.strip_margin() + assert delim_after_first_instance_stripped == delim_after_first_instance +} + +fn test_strip_margins_uneven_delims() { + uneven_delims := ['It doesn\'t matter if the delims are uneven,', + 'The text will still be delimited correctly.', + 'Maybe not everything needs 3 lines?', + 'Let us go for 4 then', + ].join('\n') + uneven_delims_stripped := 'It doesn\'t matter if the delims are uneven, + |The text will still be delimited correctly. + |Maybe not everything needs 3 lines? + |Let us go for 4 then'.strip_margin() + assert uneven_delims_stripped == uneven_delims +} + +fn test_strip_margins_multiple_blank_lines() { + multi_blank_lines := ['Multiple blank lines will be removed.', + ' I actually consider this a feature.', + ].join('\n') + multi_blank_lines_stripped := 'Multiple blank lines will be removed. + + + + | I actually consider this a feature.'.strip_margin() + assert multi_blank_lines == multi_blank_lines_stripped +} + +fn test_strip_margins_end_newline() { + end_with_newline := ['This line will end with a newline', + 'Something cool or something.', + '', + ].join('\n') + end_with_newline_stripped := 'This line will end with a newline + |Something cool or something. + + '.strip_margin() + assert end_with_newline_stripped == end_with_newline +} + +fn test_strip_margins_space_delimiter() { + space_delimiter := ['Using a white-space char will', + 'revert back to default behavior.', + ].join('\n') + space_delimiter_stripped := 'Using a white-space char will + |revert back to default behavior.'.strip_margin(`\n`) + assert space_delimiter == space_delimiter_stripped +} + +fn test_strip_margins_crlf() { + crlf := ['This string\'s line endings have CR as well as LFs.', + 'This should pass', + 'Definitely', + ].join('\r\n') + crlf_stripped := 'This string\'s line endings have CR as well as LFs.\r + |This should pass\r + |Definitely'.strip_margin() + + assert crlf == crlf_stripped +} diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index e1561f8283..85efbb8875 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -20,11 +20,6 @@ fn test_add() { assert a.ends_with('bbbbb') a += '123' assert a.ends_with('3') - mut foo := Foo{10, 'hi'} - assert foo.str == 'hi' - assert foo.bar == 10 - foo.str += '!' - assert foo.str == 'hi!' } fn test_ends_with() { @@ -481,20 +476,6 @@ fn test_reverse() { assert 'a'.reverse() == 'a' } -fn (f Foo) baz() string { - return 'baz' -} - -fn test_interpolation() { - num := 7 - mut s := 'number=$num' - assert s == 'number=7' - foo := Foo{} - s = 'baz=${foo.baz()}' - assert s == 'baz=baz' - -} - fn test_bytes_to_string() { mut buf := vcalloc(10) buf[0] = `h` @@ -703,115 +684,3 @@ fn test_split_into_lines() { } } -fn test_strip_margins_no_tabs() { - no_tabs := ['Hello there', - 'This is a string', - 'With multiple lines', - ].join('\n') - no_tabs_stripped := 'Hello there - |This is a string - |With multiple lines'.strip_margin() - assert no_tabs == no_tabs_stripped -} - -fn test_strip_margins_text_before() { - text_before := ['There is text', - 'before the delimiter', - 'that should be removed as well', - ].join('\n') - text_before_stripped := 'There is text - f lasj asldfj j lksjdf |before the delimiter - Which is removed hello |that should be removed as well'.strip_margin() - assert text_before_stripped == text_before -} - -fn test_strip_margins_white_space_after_delim() { - tabs := [' Tab', - ' spaces', - ' another tab', - ].join('\n') - tabs_stripped := ' Tab - | spaces - | another tab'.strip_margin() - assert tabs == tabs_stripped -} - -fn test_strip_margins_alternate_delim() { - alternate_delimiter := ['This has a different delim,', - 'but that is ok', - 'because everything works', - ].join('\n') - alternate_delimiter_stripped := 'This has a different delim, - #but that is ok - #because everything works'.strip_margin(`#`) - assert alternate_delimiter_stripped == alternate_delimiter -} - -fn test_strip_margins_multiple_delims_after_first() { - delim_after_first_instance := ['The delimiter used', - 'only matters the |||| First time it is seen', - 'not any | other | times', - ].join('\n') - delim_after_first_instance_stripped := 'The delimiter used - |only matters the |||| First time it is seen - |not any | other | times'.strip_margin() - assert delim_after_first_instance_stripped == delim_after_first_instance -} - -fn test_strip_margins_uneven_delims() { - uneven_delims := ['It doesn\'t matter if the delims are uneven,', - 'The text will still be delimited correctly.', - 'Maybe not everything needs 3 lines?', - 'Let us go for 4 then', - ].join('\n') - uneven_delims_stripped := 'It doesn\'t matter if the delims are uneven, - |The text will still be delimited correctly. - |Maybe not everything needs 3 lines? - |Let us go for 4 then'.strip_margin() - assert uneven_delims_stripped == uneven_delims -} - -fn test_strip_margins_multiple_blank_lines() { - multi_blank_lines := ['Multiple blank lines will be removed.', - ' I actually consider this a feature.', - ].join('\n') - multi_blank_lines_stripped := 'Multiple blank lines will be removed. - - - - | I actually consider this a feature.'.strip_margin() - assert multi_blank_lines == multi_blank_lines_stripped -} - -fn test_strip_margins_end_newline() { - end_with_newline := ['This line will end with a newline', - 'Something cool or something.', - '', - ].join('\n') - end_with_newline_stripped := 'This line will end with a newline - |Something cool or something. - - '.strip_margin() - assert end_with_newline_stripped == end_with_newline -} - -fn test_strip_margins_space_delimiter() { - space_delimiter := ['Using a white-space char will', - 'revert back to default behavior.', - ].join('\n') - space_delimiter_stripped := 'Using a white-space char will - |revert back to default behavior.'.strip_margin(`\n`) - assert space_delimiter == space_delimiter_stripped -} - -fn test_strip_margins_crlf() { - crlf := ['This string\'s line endings have CR as well as LFs.', - 'This should pass', - 'Definitely', - ].join('\r\n') - crlf_stripped := 'This string\'s line endings have CR as well as LFs.\r - |This should pass\r - |Definitely'.strip_margin() - - assert crlf == crlf_stripped -} diff --git a/vlib/compiler/tests/string_interpolation_test.v b/vlib/compiler/tests/string_interpolation_test.v index 280c5d0045..809bcc42ed 100644 --- a/vlib/compiler/tests/string_interpolation_test.v +++ b/vlib/compiler/tests/string_interpolation_test.v @@ -1,36 +1,57 @@ - -fn test_simple_string_interpolation(){ +fn test_simple_string_interpolation() { a := 'Hello' b := 'World' res := '$a $b' assert res == 'Hello World' } +fn test_mixed_string_interpolation() { + num := 7 + str := 'abc' + s1 := 'number=$num' + assert s1 == 'number=7' + s2 := 'string=$str' + assert s2 == 'string=abc' + s3 := 'a: $num | b: $str' + assert s3 == 'a: 7 | b: abc' +} + +fn test_formatted_string_interpolation() { + x := 'abc' + axb := 'a:$x:b' + assert axb == 'a:abc:b' + x_10 := 'a:${x:10s}:b' + x10_ := 'a:${x:-10s}:b' + assert x_10 == 'a: abc:b' + assert x10_ == 'a:abc :b' + i := 23 + si_right := '${i:10d}' + si__left := '${i:-10d}' + assert si_right == ' 23' + assert si__left == '23 ' +} + fn test_excape_dollar_in_string() { - i := 42 - - assert '($i)' == '(42)' - assert '(\$i)'.contains('i') && !'(\$i)'.contains('42') - assert !'(\\$i)'.contains('i') && '(\\$i)'.contains('42') && '(\\$i)'.contains('\\') - assert '(\\\$i)'.contains('i') && !'(\\\$i)'.contains('42') && '(\\$i)'.contains('\\') - assert !'(\\\\$i)'.contains('i') && '(\\\\$i)'.contains('42') && '(\\\\$i)'.contains('\\\\') - - assert '(${i})' == '(42)' - assert '(\${i})'.contains('i') && !'(\${i})'.contains('42') - assert !'(\\${i})'.contains('i') && '(\\${i})'.contains('42') && '(\\${i})'.contains('\\') - assert '(\\\${i})'.contains('i') && !'(\\\${i})'.contains('42') && '(\\${i})'.contains('\\') - assert !'(\\\\${i})'.contains('i') && '(\\\\${i})'.contains('42') && '(\\\\${i})'.contains('\\\\') - assert i==42 + i := 42 + assert '($i)' == '(42)' + assert '(\$i)'.contains('i') && !'(\$i)'.contains('42') + assert !'(\\$i)'.contains('i') && '(\\$i)'.contains('42') && '(\\$i)'.contains('\\') + assert '(\\\$i)'.contains('i') && !'(\\\$i)'.contains('42') && '(\\$i)'.contains('\\') + assert !'(\\\\$i)'.contains('i') && '(\\\\$i)'.contains('42') && '(\\\\$i)'.contains('\\\\') + assert '(${i})' == '(42)' + assert '(\${i})'.contains('i') && !'(\${i})'.contains('42') + assert !'(\\${i})'.contains('i') && '(\\${i})'.contains('42') && '(\\${i})'.contains('\\') + assert '(\\\${i})'.contains('i') && !'(\\\${i})'.contains('42') && '(\\${i})'.contains('\\') + assert !'(\\\\${i})'.contains('i') && '(\\\\${i})'.contains('42') && '(\\\\${i})'.contains('\\\\') + assert i == 42 } fn test_implicit_str() { - i := 42 - assert 'int $i' == 'int 42' - assert '$i' == '42' - - check := '$i' == '42' - assert check - - text := '$i' + '42' - assert text == '4242' + i := 42 + assert 'int $i' == 'int 42' + assert '$i' == '42' + check := '$i' == '42' + assert check + text := '$i' + '42' + assert text == '4242' } diff --git a/vlib/compiler/tests/string_struct_interpolation_test.v b/vlib/compiler/tests/string_struct_interpolation_test.v new file mode 100644 index 0000000000..28b945f711 --- /dev/null +++ b/vlib/compiler/tests/string_struct_interpolation_test.v @@ -0,0 +1,25 @@ + +struct Foo { + bar int +mut: + str string +} + +fn (f Foo) baz() string { + return 'baz' +} + +fn test_string_method_interpolation() { + foo := Foo{} + s := 'baz=${foo.baz()}' + assert s == 'baz=baz' +} + +fn test_adding_to_mutable_string_field() { + mut foo := Foo{10, 'hi'} + assert foo.bar == 10 + assert foo.str == 'hi' + foo.str += '!' + eprintln( foo.str ) + assert foo.str == 'hi!' +} diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index d6d43c82b0..1e48b2844b 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -59,6 +59,9 @@ pub fn (node &FnDecl) str(t &table.Table) string { // string representaiton of expr pub fn (x Expr) str() string { match x { + Ident { + return it.name + } InfixExpr { return '(${it.left.str()} $it.op.str() ${it.right.str()})' } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index e9452e12f1..c78ab780ad 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -269,16 +269,7 @@ fn (g mut Gen) stmt(node ast.Stmt) { // g.writeln('//// stmt start') match node { ast.AssertStmt { - g.writeln('// assert') - g.write('if ((') - g.expr(it.expr) - g.writeln(')) {') - g.writeln('g_test_oks++;') - // g.writeln('puts("OK $g.fn_decl.name");') - g.writeln('} else {') - g.writeln('g_test_fails++;') - g.writeln('puts("FAILED $g.fn_decl.name $it.pos.line_nr");') - g.writeln('}') + g.gen_assert_stmt(it) } ast.AssignStmt { g.gen_assign_stmt(it) @@ -366,7 +357,7 @@ fn (g mut Gen) stmt(node ast.Stmt) { g.writeln('}') } ast.ForInStmt { - g.for_in(it) + g.for_in(it) } ast.ForStmt { g.write('while (') @@ -522,6 +513,29 @@ fn (g mut Gen) expr_with_cast(expr ast.Expr, got_type table.Type, exp_type table g.expr(expr) } +fn (g mut Gen) gen_assert_stmt(a ast.AssertStmt) { + g.writeln('// assert') + g.write('if( ') + g.expr(a.expr) + s_assertion := a.expr.str().replace('"', "\'") + g.write(' )') + if g.is_test { + g.writeln('{') + g.writeln(' g_test_oks++;') + // g.writeln(' println(_STR("OK ${g.file.path}:${a.pos.line_nr}: fn ${g.fn_decl.name}(): assert $s_assertion"));') + g.writeln('}else{') + g.writeln(' g_test_fails++;') + g.writeln(' eprintln(_STR("${g.file.path}:${a.pos.line_nr}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));') + g.writeln(' exit(1);') + g.writeln('}') + } else { + g.writeln('{}else{') + g.writeln(' eprintln(_STR("${g.file.path}:${a.pos.line_nr}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));') + g.writeln(' exit(1);') + g.writeln('}') + } +} + fn (g mut Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // g.write('/*assign_stmt*/') if assign_stmt.left.len > assign_stmt.right.len { @@ -537,7 +551,7 @@ fn (g mut Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { else { panic('expected call') } - } + } mr_var_name := 'mr_$assign_stmt.pos.pos' g.expr_var_name = mr_var_name if table.type_is_optional(return_type) { @@ -738,9 +752,9 @@ fn (g mut Gen) free_scope_vars(pos int) { continue } else { - g.writeln('// other' + t) + g.writeln('// other ' + t) } - } + } g.writeln('string_free($var.name); // autofreed') } } @@ -2031,24 +2045,39 @@ fn (g mut Gen) string_inter_literal(node ast.StringInterLiteral) { // } // else {} // } - if node.expr_types[i] == table.string_type { + + sfmt := node.expr_fmts[i] + if sfmt.len > 0 { + fspec := sfmt[sfmt.len-1] + if fspec == `s` && node.expr_types[i] != table.string_type { + verror('only V strings can be formatted with a ${sfmt} format') + } + g.write('%' + sfmt[1..]) + }else if node.expr_types[i] == table.string_type { g.write('%.*s') - } - else if node.expr_types[i] == table.int_type { + }else { g.write('%d') } } g.write('", ') // Build args for i, expr in node.exprs { - if node.expr_types[i] == table.string_type { + sfmt := node.expr_fmts[i] + if sfmt.len > 0 { + fspec := sfmt[sfmt.len-1] + if fspec == `s` && node.expr_types[i] == table.string_type { + g.expr(expr) + g.write('.str') + }else{ + g.expr(expr) + } + } else if node.expr_types[i] == table.string_type { // `name.str, name.len,` g.expr(expr) g.write('.len, ') g.expr(expr) g.write('.str') - } - else { + } else { g.expr(expr) } if i < node.exprs.len - 1 { @@ -2366,15 +2395,36 @@ fn (g &Gen) type_default(typ table.Type) string { } pub fn (g mut Gen) write_tests_main() { + g.definitions.writeln('int g_test_oks = 0;') + g.definitions.writeln('int g_test_fails = 0;') g.writeln('int main() {') g.writeln('\t_vinit();') + mut tfuncs := []string + mut tsuite_begin := '' + mut tsuite_end := '' for _, f in g.table.fns { + if f.name == 'testsuite_begin' { + tsuite_begin = f.name + } + if f.name == 'testsuite_end' { + tsuite_end = f.name + } if !f.name.starts_with('test_') { continue } - g.writeln('\t${f.name}();') + tfuncs << f.name } - g.writeln('return 0; }') + if tsuite_begin.len > 0 { + g.writeln('\t${tsuite_begin}();\n') + } + for t in tfuncs { + g.writeln('\t${t}();') + } + if tsuite_end.len > 0 { + g.writeln('\t${tsuite_end}();\n') + } + g.writeln('\treturn 0;') + g.writeln('}') } fn (g &Gen) is_importing_os() bool { diff --git a/vlib/v/gen/cheaders.v b/vlib/v/gen/cheaders.v index 34961516a0..94deb93e4b 100644 --- a/vlib/v/gen/cheaders.v +++ b/vlib/v/gen/cheaders.v @@ -186,8 +186,6 @@ extern wchar_t **_wenviron; //================================== GLOBALS =================================*/ byte g_str_buf[1024]; -int g_test_fails = 0; -int g_test_oks = 0; int load_so(byteptr); void reload_so(); void _vinit();