Skip to content

Commit

Permalink
checker: more line-info logic
Browse files Browse the repository at this point in the history
  • Loading branch information
medvednikov committed Sep 10, 2023
1 parent 12dd6e8 commit b9a233b
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 94 deletions.
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- [ ] 64/32 bit int depending on arch (will remove array.len limitation on 64 bit systems)
- [ ] `copy()` builtin function (e.g. for easier conversion from `[]Foo` to `[4]Foo`)
- [ ] Lambdas: `a.sort(|a, b| a > b)`
- [ ] Custom attributes.

## [Version 1.0]

Expand Down
1 change: 1 addition & 0 deletions vlib/os/file.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -880,3 +880,4 @@ pub fn (f &File) tell() !i64 {
}
return pos
}

7 changes: 6 additions & 1 deletion vlib/os/os.v
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,12 @@ pub fn join_path(base string, dirs ...string) string {
sb.write_string(path_separator)
sb.write_string(d)
}
return sb.str()
mut res := sb.str()
if res.contains('/./') {
// Fix `join_path("/foo/bar", "./file.txt")` => `/foo/bar/./file.txt`
res = res.replace('/./', '/')
}
return res
}

// join_path_single appends the `elem` after `base`, using a platform specific
Expand Down
1 change: 1 addition & 0 deletions vlib/os/os_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ fn test_join() {
assert os.join_path('v', 'vlib', 'os') == 'v\\vlib\\os'
} $else {
assert os.join_path('v', 'vlib', 'os') == 'v/vlib/os'
assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/file.txt'
}
}

Expand Down
93 changes: 93 additions & 0 deletions vlib/v/checker/autocomplete.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker

import strings
import v.ast
import os

fn (mut c Checker) ident_autocomplete(node ast.Ident) {
// Mini LS hack (v -line-info "a.v:16")
println(
'checker.ident() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' +
' pwd="${os.getwd()}" file="${c.file.path}", ' +
' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" expr="${c.pref.linfo.expr}"')
// Make sure this ident is on the same line as requeste, in the same file, and has the same name
same_line := node.pos.line_nr in [c.pref.linfo.line_nr - 1, c.pref.linfo.line_nr + 1, c.pref.linfo.line_nr]
if !same_line {
return
}
same_name := c.pref.linfo.expr == node.name
if !same_name {
return
}
abs_path := os.join_path(os.getwd(), c.file.path)
if c.pref.linfo.path !in [c.file.path, abs_path] {
return
}
mut sb := strings.new_builder(10)
if node.kind == .unresolved {
// println(node)
eprintln('unresolved type, maybe "${node.name}" was not defined. otherwise this is a bug, should never happen; please report')
exit(1)
}
sym := c.table.sym(c.unwrap_generic(node.obj.typ))
// sb.writeln('VAR ${node.name}:${sym.name} ${node.pos.line_nr}')
nt := '${node.name}:${sym.name}'
if !c.pref.linfo.vars_printed[nt] { // avoid dups
sb.writeln('===')
sb.writeln('VAR ${nt}') //${node.name}:${sym.name}')
/// print_backtrace()
/*
if sym.kind == .alias {
parent_sym := c.table.sym(sym.parent_type)
}
*/

mut fields := []ACFieldMethod{cap: 10}
if sym.kind == .struct_ {
// Add fields, but only if it's a struct.
struct_info := sym.info as ast.Struct
// match struct_info {
// ast.Struct
//}
for field in struct_info.fields {
field_sym := c.table.sym(field.typ)
fields << ACFieldMethod{field.name, field_sym.name}
}
} else if sym.kind == .array {
// t := typeof(sym.info).name
if sym.info is ast.Aggregate {
} else if sym.info is ast.Array {
fields << ACFieldMethod{'len', 'int'}
fields << ACFieldMethod{'cap', 'int'}
}
// array_info := sym.info as ast.Array
}
// Aliases and other types can have methods, add them
for method in sym.methods {
method_ret_type := c.table.sym(method.return_type)
fields << ACFieldMethod{build_method_summary(method), method_ret_type.name}
}
fields.sort(a.name < b.name)
for field in fields {
sb.writeln('${field.name}:${field.typ}')
}
res := sb.str().trim_space()
if res != '' {
println(res)
c.pref.linfo.vars_printed[nt] = true
}
}
}

fn build_method_summary(method ast.Fn) string {
mut s := method.name + '('
for i, param in method.params {
s += param.name
if i < method.params.len - 1 {
s += ','
}
}
return s + ')'
}
74 changes: 34 additions & 40 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import v.util.version
import v.errors
import v.pkgconfig
import v.transformer
import strings

const (
int_min = int(0x80000000)
Expand Down Expand Up @@ -44,8 +43,9 @@ pub const (

[heap; minify]
pub struct Checker {
pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
pub mut:
pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
//
table &ast.Table = unsafe { nil }
file &ast.File = unsafe { nil }
nr_errors int
Expand Down Expand Up @@ -123,13 +123,13 @@ mut:
inside_decl_rhs bool
inside_if_guard bool // true inside the guard condition of `if x := opt() {}`
inside_assign bool
doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
doing_line_path string // same, but stores the path being parsed
is_index_assign bool
comptime_call_pos int // needed for correctly checking use before decl for templates
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
enum_data_type ast.Type
fn_return_type ast.Type
// doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
// doing_line_path string // same, but stores the path being parsed
is_index_assign bool
comptime_call_pos int // needed for correctly checking use before decl for templates
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
enum_data_type ast.Type
fn_return_type ast.Type
}

pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker {
Expand Down Expand Up @@ -293,6 +293,7 @@ pub fn (mut c Checker) change_current_file(file &ast.File) {
}

pub fn (mut c Checker) check_files(ast_files []&ast.File) {
// println('check_files')
// c.files = ast_files
mut has_main_mod_file := false
mut has_main_fn := false
Expand Down Expand Up @@ -381,9 +382,27 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
c.error('a _test.v file should have *at least* one `test_` function', token.Pos{})
}
}
// Print line info and exit
if c.pref.line_info != '' && c.doing_line_info == 0 {
c.do_line_info(c.pref.line_info, ast_files)
// After the main checker run, run the line info check, print line info, and exit (if it's present)
if c.pref.line_info != '' && !c.pref.linfo.is_running { //'' && c.pref.linfo.line_nr == 0 {
// c.do_line_info(c.pref.line_info, ast_files)
println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd())
c.pref.linfo.is_running = true
for i, file in ast_files {
// println(file.path)
if file.path == c.pref.linfo.path {
println('running c.check_files')
c.check_files([ast_files[i]])
exit(0)
} else if file.path.starts_with('./') {
// Maybe it's a "./foo.v", linfo.path has an absolute path
abs_path := os.join_path(os.getwd(), file.path).replace('/./', '/') // TODO join_path shouldn't have /./
if abs_path == c.pref.linfo.path {
c.check_files([ast_files[i]])
exit(0)
}
}
}
println('failed to find file "${c.pref.linfo.path}"')
exit(0)
}
// Make sure fn main is defined in non lib builds
Expand Down Expand Up @@ -1605,7 +1624,7 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
return ast.int_type
}

c.error('`${unwrapped_sym.name}` has no property `${node.field_name}`', node.pos)
c.error('`${unwrapped_sym.name}` 1has no property `${node.field_name}`', node.pos)
}
} else {
if sym.info is ast.Struct {
Expand Down Expand Up @@ -3362,34 +3381,9 @@ struct ACFieldMethod {
}

fn (mut c Checker) ident(mut node ast.Ident) ast.Type {
if c.doing_line_info > 0 {
mut sb := strings.new_builder(10)
if c.pref.linfo.is_running {
// Mini LS hack (v -line-info "a.v:16")
// println('line_nr=${node.pos.line_nr} doing line nr=${c.doing_line_info}')
// println('Start line_nr=${node.pos.line_nr} line2=${c.doing_line_info} file="${c.file.path}", pppp="${c.doing_line_path}"')
if node.pos.line_nr == c.doing_line_info && c.file.path == c.doing_line_path {
sb.writeln('===')
sym := c.table.sym(node.obj.typ)
sb.writeln('VAR ${node.name}:${sym.name}')
mut struct_info := sym.info as ast.Struct
mut fields := []ACFieldMethod{cap: struct_info.fields.len}
for field in struct_info.fields {
field_sym := c.table.sym(field.typ)
fields << ACFieldMethod{field.name, field_sym.name}
}
for method in sym.methods {
method_ret_type := c.table.sym(method.return_type)
fields << ACFieldMethod{method.name + '()', method_ret_type.name}
}
fields.sort(a.name < b.name)
for field in fields {
sb.writeln('${field.name}:${field.typ}')
}
res := sb.str().trim_space()
if res != '' {
println(res)
}
}
c.ident_autocomplete(node)
}
// TODO: move this
if c.const_deps.len > 0 {
Expand Down
52 changes: 0 additions & 52 deletions vlib/v/checker/line_info.v

This file was deleted.

72 changes: 72 additions & 0 deletions vlib/v/pref/line_info.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module pref

// import v.ast
// import v.pref
// import os

// fn (mut p Pref) parse_line_info(line string, all_ast_files []&ast.File) {
fn (mut p Preferences) parse_line_info(line string) {
// println("parse_line_info '${line}'")
format_err := 'wrong format, use `-line-info "file.v:24:expr_to_look_up"'
vals := line.split(':')
if vals.len != 3 {
eprintln(format_err)
return
}
file_name := vals[0]
line_nr := vals[1].int() - 1
expr := vals[2]
if !file_name.ends_with('.v') || line_nr == -1 {
eprintln(format_err)
return
}

// println('files.len=${c.files.len}')
// Find which file contains the line
mut found := true // false
//// mut found_path := ''
// mut found_file_idx := -1
/*
for i, file in all_ast_files {
// base := os.base(file.path)
base := file.path // os.base(file.path)
// println(base)
if base == file_name {
if found {
eprintln('more than one "${file_name}" found: "${file.path}" and "${found_path}"')
return
}
found = true
found_path = file.path
found_file_idx = i
}
}
*/

if !found {
eprintln('file "${file_name}" not found among those parsed')
return
}

p.linfo = LineInfo{
line_nr: line_nr
path: file_name
expr: expr
}
}

pub fn add_line_info_expr_to_program_text(raw_text string, linfo LineInfo) string {
lines := raw_text.split('\n')
lines_before := lines[..linfo.line_nr].join('\n')
mut expr := linfo.expr
if !expr.contains('.') {
// Single variable, `foo` => `foo.xx`
// expr += '.xxx'
// expr = '_ = ' + expr
expr = 'println(' + expr + ')'
}
lines_after := lines[linfo.line_nr..].join('\n')
return lines_before + '\n' + expr + '\n' + lines_after
}

0 comments on commit b9a233b

Please sign in to comment.