diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 862c3f95c4..8625bc466f 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1210,6 +1210,7 @@ pub fn (expr Expr) position() token.Position { line_nr: expr.pos.line_nr pos: left_pos.pos len: right_pos.pos - left_pos.pos + right_pos.len + last_line: right_pos.last_line } } CTempVar { diff --git a/vlib/v/checker/tests/prefix_expr_decl_assign_err.out b/vlib/v/checker/tests/prefix_expr_decl_assign_err.out index 00bb16b5bb..575fc0f311 100644 --- a/vlib/v/checker/tests/prefix_expr_decl_assign_err.out +++ b/vlib/v/checker/tests/prefix_expr_decl_assign_err.out @@ -4,9 +4,9 @@ vlib/v/checker/tests/prefix_expr_decl_assign_err.vv:2:5: error: non-name on the | ^ 3 | (*d) := 14 4 | } -vlib/v/checker/tests/prefix_expr_decl_assign_err.vv:3:10: error: non-name `(*d)` on left side of `:=` +vlib/v/checker/tests/prefix_expr_decl_assign_err.vv:3:5: error: non-name `(*d)` on left side of `:=` 1 | fn main() { 2 | &a := 12 3 | (*d) := 14 - | ~~ + | ~~~~ 4 | } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 7622d7ef79..7955ad85da 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -339,8 +339,18 @@ pub fn (f Fmt) imp_stmt_str(imp ast.Import) string { pub fn (mut f Fmt) stmts(stmts []ast.Stmt) { f.indent++ + mut prev_line_nr := 0 + if stmts.len >= 1 { + prev_pos := stmts[0].position() + prev_line_nr = util.imax(prev_pos.line_nr, prev_pos.last_line) + } for stmt in stmts { + if stmt.position().line_nr - prev_line_nr > 1 { + f.out.writeln('') + } f.stmt(stmt) + prev_pos := stmt.position() + prev_line_nr = util.imax(prev_pos.line_nr, prev_pos.last_line) } f.indent-- } diff --git a/vlib/v/fmt/tests/empty_lines_expected.vv b/vlib/v/fmt/tests/empty_lines_expected.vv new file mode 100644 index 0000000000..f9328e0ff1 --- /dev/null +++ b/vlib/v/fmt/tests/empty_lines_expected.vv @@ -0,0 +1,21 @@ +fn squash_multiple_empty_lines() { + println('a') + + println('b') + + c := 0 + + d := 0 +} + +fn remove_leading_and_trailing_empty_lines() { + println('a') + + println('b') + + if test { + c := 0 + } else { + d := 0 + } +} diff --git a/vlib/v/fmt/tests/empty_lines_input.vv b/vlib/v/fmt/tests/empty_lines_input.vv new file mode 100644 index 0000000000..acd8b164f5 --- /dev/null +++ b/vlib/v/fmt/tests/empty_lines_input.vv @@ -0,0 +1,30 @@ +fn squash_multiple_empty_lines() { + println('a') + + + println('b') + + + c := 0 + + + d := 0 +} + +fn remove_leading_and_trailing_empty_lines() { + + println('a') + + println('b') + + if test { + + c := 0 + + } else { + + d := 0 + + } + +} diff --git a/vlib/v/fmt/tests/empty_lines_keep.vv b/vlib/v/fmt/tests/empty_lines_keep.vv new file mode 100644 index 0000000000..ce916657b3 --- /dev/null +++ b/vlib/v/fmt/tests/empty_lines_keep.vv @@ -0,0 +1,34 @@ +fn keep_single_empty_line() { + println('a') + println('b') + + println('c') + + d := 0 + + if true { + println('e') + } + + f := 0 +} + +fn prevent_empty_line_after_multi_line_statements() { + // line1 + /* + block1 + */ + /* + block2 + */ + if test { + println('a') + } + println('b') + for test { + println('c') + } + c := fn (s string) { + println('s') + } +} diff --git a/vlib/v/fmt/tests/expressions_expected.vv b/vlib/v/fmt/tests/expressions_expected.vv index 149bb1b009..d0a13ca6f0 100644 --- a/vlib/v/fmt/tests/expressions_expected.vv +++ b/vlib/v/fmt/tests/expressions_expected.vv @@ -35,6 +35,7 @@ fn string_inter_lit(mut c checker.Checker, mut node ast.StringInterLiteral) tabl } node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ) } + return table.string_type } diff --git a/vlib/v/fmt/tests/go_stmt_expected.vv b/vlib/v/fmt/tests/go_stmt_expected.vv index f4d2a97b3e..5e26722ec9 100644 --- a/vlib/v/fmt/tests/go_stmt_expected.vv +++ b/vlib/v/fmt/tests/go_stmt_expected.vv @@ -9,5 +9,6 @@ fn my_thread_with_params(s string) { fn my_fn_calling_threads() { go my_thread() go my_thread_with_params('yay') + go my_thread_with_params('nono') } diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index f6a5ad14e1..df49921e05 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -88,7 +88,7 @@ fn (mut p Parser) check_cross_variables(exprs []ast.Expr, val ast.Expr) bool { fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comment) ast.Stmt { p.is_stmt_ident = false op := p.tok.kind - pos := p.tok.position() + mut pos := p.tok.position() p.next() mut comments := []ast.Comment{cap: 2 * left_comments.len + 1} comments << left_comments @@ -195,6 +195,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme } } } + pos.update_last_line(p.prev_tok.line_nr) return ast.AssignStmt{ op: op left: left diff --git a/vlib/v/parser/containers.v b/vlib/v/parser/containers.v index ebf6a4d0e2..40fc3568fe 100644 --- a/vlib/v/parser/containers.v +++ b/vlib/v/parser/containers.v @@ -130,7 +130,7 @@ fn (mut p Parser) array_init() ast.ArrayInit { } p.check(.rcbr) } - pos := first_pos.extend(last_pos) + pos := first_pos.extend_with_last_line(last_pos, p.prev_tok.line_nr) return ast.ArrayInit{ is_fixed: is_fixed has_val: has_val @@ -151,7 +151,7 @@ fn (mut p Parser) array_init() ast.ArrayInit { } fn (mut p Parser) map_init() ast.MapInit { - pos := p.tok.position() + mut pos := p.tok.position() mut keys := []ast.Expr{} mut vals := []ast.Expr{} for p.tok.kind != .rcbr && p.tok.kind != .eof { @@ -167,6 +167,7 @@ fn (mut p Parser) map_init() ast.MapInit { p.next() } } + pos.update_last_line(p.tok.line_nr) return ast.MapInit{ keys: keys vals: vals diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 32bf0fc42b..33cb7dd55e 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -57,7 +57,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp if p.tok.kind == .not { p.next() } - pos := first_pos.extend(last_pos) + mut pos := first_pos.extend(last_pos) mut or_stmts := []ast.Stmt{} // TODO remove unnecessary allocations by just using .absent mut or_pos := p.tok.position() if p.tok.kind == .key_orelse { @@ -93,6 +93,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp fn_name = p.imported_symbols[fn_name] } comments := p.eat_line_end_comments() + pos.update_last_line(p.prev_tok.line_nr) return ast.CallExpr{ name: fn_name args: args @@ -435,7 +436,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } fn (mut p Parser) anon_fn() ast.AnonFn { - pos := p.tok.position() + mut pos := p.tok.position() p.check(.key_fn) if p.pref.is_script && p.tok.kind == .name { p.error_with_pos('function declarations in script mode should be before all script statements', @@ -487,6 +488,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { idx := p.table.find_or_register_fn_type(p.mod, func, true, false) typ := table.new_type(idx) // name := p.table.get_type_name(typ) + pos.update_last_line(p.prev_tok.line_nr) return ast.AnonFn{ decl: ast.FnDecl{ name: name diff --git a/vlib/v/parser/for.v b/vlib/v/parser/for.v index 4f087274c4..b034037391 100644 --- a/vlib/v/parser/for.v +++ b/vlib/v/parser/for.v @@ -20,7 +20,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { if p.tok.kind == .lcbr { p.inside_for = false stmts := p.parse_block_no_scope(false) - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) for_stmt := ast.ForStmt{ stmts: stmts pos: pos @@ -64,7 +64,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { } p.inside_for = false stmts := p.parse_block_no_scope(false) - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) for_c_stmt := ast.ForCStmt{ stmts: stmts has_init: has_init @@ -159,7 +159,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { } p.inside_for = false stmts := p.parse_block_no_scope(false) - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) // println('nr stmts=$stmts.len') for_in_stmt := ast.ForInStmt{ stmts: stmts @@ -181,7 +181,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { // extra scope for the body p.open_scope() stmts := p.parse_block_no_scope(false) - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) for_stmt := ast.ForStmt{ cond: cond stmts: stmts diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 615d849b5c..e878b9d566 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -15,7 +15,7 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { p.inside_ct_if_expr = was_inside_ct_if_expr } p.inside_if_expr = true - pos := if is_comptime { + mut pos := if is_comptime { p.inside_ct_if_expr = true p.next() // `$` p.prev_tok.position().extend(p.tok.position()) @@ -143,6 +143,10 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { break } } + pos.update_last_line(p.prev_tok.line_nr) + if comments.len > 0 { + pos.last_line = comments.last().pos.last_line + } return ast.IfExpr{ is_comptime: is_comptime branches: branches @@ -235,8 +239,12 @@ fn (mut p Parser) match_expr() ast.MatchExpr { branch_scope := p.scope p.close_scope() p.inside_match_body = false - pos := branch_first_pos.extend(branch_last_pos) + mut pos := branch_first_pos.extend(branch_last_pos) post_comments := p.eat_comments() + pos.update_last_line(p.prev_tok.line_nr) + if post_comments.len > 0 { + pos.last_line = post_comments.last().pos.last_line + } branches << ast.MatchBranch{ exprs: exprs ecmnts: ecmnts @@ -255,7 +263,7 @@ fn (mut p Parser) match_expr() ast.MatchExpr { } } match_last_pos := p.tok.position() - pos := token.Position{ + mut pos := token.Position{ line_nr: match_first_pos.line_nr pos: match_first_pos.pos len: match_last_pos.pos - match_first_pos.pos + match_last_pos.len @@ -264,6 +272,7 @@ fn (mut p Parser) match_expr() ast.MatchExpr { p.check(.rcbr) } // return ast.StructInit{} + pos.update_last_line(p.prev_tok.line_nr) return ast.MatchExpr{ branches: branches cond: cond @@ -397,12 +406,16 @@ fn (mut p Parser) select_expr() ast.SelectExpr { stmts := p.parse_block_no_scope(false) p.close_scope() p.inside_match_body = false - pos := token.Position{ + mut pos := token.Position{ line_nr: branch_first_pos.line_nr pos: branch_first_pos.pos len: branch_last_pos.pos - branch_first_pos.pos + branch_last_pos.len } post_comments := p.eat_comments() + pos.update_last_line(p.prev_tok.line_nr) + if post_comments.len > 0 { + pos.last_line = post_comments.last().pos.last_line + } branches << ast.SelectBranch{ stmt: stmt stmts: stmts @@ -427,7 +440,7 @@ fn (mut p Parser) select_expr() ast.SelectExpr { } return ast.SelectExpr{ branches: branches - pos: pos + pos: pos.extend_with_last_line(p.prev_tok.position(), p.prev_tok.line_nr) has_exception: has_else || has_timeout } } diff --git a/vlib/v/parser/lock.v b/vlib/v/parser/lock.v index 3273265a1c..c69d6463ce 100644 --- a/vlib/v/parser/lock.v +++ b/vlib/v/parser/lock.v @@ -6,7 +6,7 @@ import v.table fn (mut p Parser) lock_expr() ast.LockExpr { // TODO Handle aliasing sync p.register_auto_import('sync') - pos := p.tok.position() + mut pos := p.tok.position() is_rlock := p.tok.kind == .key_rlock p.next() mut lockeds := []ast.Ident{} @@ -27,6 +27,7 @@ fn (mut p Parser) lock_expr() ast.LockExpr { p.check(.comma) } stmts := p.parse_block() + pos.update_last_line(p.prev_tok.line_nr) return ast.LockExpr{ lockeds: lockeds stmts: stmts diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 6ed3222a19..62c9fe9384 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -569,8 +569,9 @@ pub fn (mut p Parser) check_comment() ast.Comment { } pub fn (mut p Parser) comment() ast.Comment { - pos := p.tok.position() + mut pos := p.tok.position() text := p.tok.lit + pos.last_line = pos.line_nr + text.count('\n') p.next() // p.next_with_comment() return ast.Comment{ @@ -629,11 +630,12 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } .key_assert { p.next() - assert_pos := p.tok.position() + mut pos := p.tok.position() expr := p.expr(0) + pos.update_last_line(p.prev_tok.line_nr) return ast.AssertStmt{ expr: expr - pos: assert_pos + pos: pos } } .key_for { @@ -692,16 +694,24 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { .dollar { match p.peek_tok.kind { .key_if { + mut pos := p.tok.position() + expr := p.if_expr(true) + pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ - expr: p.if_expr(true) + expr: expr + pos: pos } } .key_for { return p.comp_for() } .name { + mut pos := p.tok.position() + expr := p.comp_call() + pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ - expr: p.comp_call() + expr: expr + pos: pos } } else { @@ -985,6 +995,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { // a, mut b ... :=/= // multi-assign // collect things upto hard boundaries tok := p.tok + mut pos := tok.position() left, left_comments := p.expr_list() left0 := left[0] if tok.kind == .key_mut && p.tok.kind != .decl_assign { @@ -1002,6 +1013,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { p.error_with_pos('expression evaluated but not used', left0.position()) return ast.Stmt{} } + pos.update_last_line(p.prev_tok.line_nr) if left.len == 1 { return ast.ExprStmt{ expr: left0 @@ -1015,7 +1027,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { vals: left pos: tok.position() } - pos: tok.position() + pos: pos comments: left_comments } } @@ -2417,7 +2429,7 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { } if p.tok.kind == .rcbr { // `unsafe {}` - pos.last_line = p.tok.line_nr - 1 + pos.update_last_line(p.tok.line_nr) p.next() return ast.Block{ is_unsafe: true @@ -2436,7 +2448,7 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { // `unsafe {expr}` if stmt.expr.is_expr() { p.next() - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) ue := ast.UnsafeExpr{ expr: stmt.expr pos: pos @@ -2456,6 +2468,7 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { stmts << p.stmt(false) } p.next() + pos.update_last_line(p.tok.line_nr) return ast.Block{ stmts: stmts is_unsafe: true diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index e136c70a42..78bd5b895f 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -95,12 +95,13 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.parse_number_literal() } .lpar { + mut pos := p.tok.position() p.check(.lpar) node = p.expr(0) p.check(.rpar) node = ast.ParExpr{ expr: node - pos: p.tok.position() + pos: pos.extend(p.prev_tok.position()) } } .key_if { @@ -118,7 +119,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.check(.lcbr) e := p.expr(0) p.check(.rcbr) - pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) node = ast.UnsafeExpr{ expr: e pos: pos @@ -315,9 +316,10 @@ pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_iden } else if p.tok.kind == .left_shift && p.is_stmt_ident { // arr << elem tok := p.tok - pos := tok.position() + mut pos := tok.position() p.next() right := p.expr(precedence - 1) + pos.update_last_line(p.prev_tok.line_nr) node = ast.InfixExpr{ left: node right: right @@ -370,7 +372,13 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { // mut typ := p. // println('infix op=$op.str()') precedence := p.tok.precedence() - pos := p.tok.position() + mut pos := p.tok.position() + if left.position().line_nr < pos.line_nr { + pos = { + pos | + line_nr: left.position().line_nr + } + } p.next() mut right := ast.Expr{} prev_expecting_type := p.expecting_type @@ -415,6 +423,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { } p.or_is_handled = false } + pos.update_last_line(p.prev_tok.line_nr) return ast.InfixExpr{ left: left right: right @@ -429,7 +438,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { } fn (mut p Parser) prefix_expr() ast.PrefixExpr { - pos := p.tok.position() + mut pos := p.tok.position() op := p.tok.kind if op == .amp { p.is_amp = true @@ -482,6 +491,7 @@ fn (mut p Parser) prefix_expr() ast.PrefixExpr { } p.or_is_handled = false } + pos.update_last_line(p.prev_tok.line_nr) return ast.PrefixExpr{ op: op right: right diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 71d3303aa8..5e7ac1bc39 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -107,7 +107,7 @@ fn (mut p Parser) sql_expr() ast.Expr { // insert user into User // update User set nr_oders=nr_orders+1 where id == user_id fn (mut p Parser) sql_stmt() ast.SqlStmt { - pos := p.tok.position() + mut pos := p.tok.position() p.inside_match = true defer { p.inside_match = false @@ -194,6 +194,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { where_expr = p.expr(0) } p.check(.rcbr) + pos.last_line = p.prev_tok.line_nr return ast.SqlStmt{ db_expr: db_expr table_name: table_name diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 081d228af0..b059375370 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -429,7 +429,7 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { fn (mut p Parser) interface_decl() ast.InterfaceDecl { p.top_level_statement_start() - mut start_pos := p.tok.position() + mut pos := p.tok.position() is_pub := p.tok.kind == .key_pub if is_pub { p.next() @@ -506,12 +506,12 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { } p.top_level_statement_end() p.check(.rcbr) - start_pos.last_line = p.prev_tok.line_nr - 1 + pos.update_last_line(p.prev_tok.line_nr) return ast.InterfaceDecl{ name: interface_name methods: methods is_pub: is_pub - pos: start_pos + pos: pos pre_comments: pre_comments } } diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index a03cd5e075..39a62732ec 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -161,6 +161,20 @@ fn (mut s Scanner) new_token(tok_kind token.Kind, lit string, len int) token.Tok } } +[inline] +fn (mut s Scanner) new_mulitline_token(tok_kind token.Kind, lit string, len int, start_line int) token.Token { + cidx := s.tidx + s.tidx++ + return token.Token{ + kind: tok_kind + lit: lit + line_nr: start_line + 1 + pos: s.pos - len + 1 + len: len + tidx: cidx + } +} + [inline] fn (mut s Scanner) ident_name() string { start := s.pos @@ -938,6 +952,7 @@ fn (mut s Scanner) text_scan() token.Token { // Multiline comments if nextc == `*` { start := s.pos + 2 + start_line := s.line_nr mut nest_count := 1 // Skip comment for nest_count > 0 && s.pos < s.text.len - 1 { @@ -964,7 +979,8 @@ fn (mut s Scanner) text_scan() token.Token { if !comment.contains('\n') { comment = '\x01' + comment } - return s.new_token(.comment, comment, comment.len + 4) + return s.new_mulitline_token(.comment, comment, comment.len + 4, + start_line) } // Skip if not in fmt mode continue diff --git a/vlib/v/token/position.v b/vlib/v/token/position.v index c8016c91ab..4a896be17d 100644 --- a/vlib/v/token/position.v +++ b/vlib/v/token/position.v @@ -20,6 +20,7 @@ pub fn (pos Position) extend(end Position) Position { return { pos | len: end.pos - pos.pos + end.len + last_line: end.last_line } } @@ -32,11 +33,16 @@ pub fn (pos Position) extend_with_last_line(end Position, last_line int) Positio } } +pub fn (mut pos Position) update_last_line(last_line int) { + pos.last_line = last_line - 1 +} + [inline] pub fn (tok &Token) position() Position { return Position{ len: tok.len line_nr: tok.line_nr - 1 pos: tok.pos + last_line: tok.line_nr - 1 } }