mirror of
https://github.com/vlang/v.git
synced 2025-09-15 18:36:37 -04:00
v: $dbg statement - native V debugger REPL (#20533)
This commit is contained in:
parent
45e13ea02a
commit
9f6448e30e
@ -440,6 +440,7 @@ fn (t Tree) stmt(node ast.Stmt) &Node {
|
||||
ast.AsmStmt { return t.asm_stmt(node) }
|
||||
ast.NodeError { return t.node_error(node) }
|
||||
ast.EmptyStmt { return t.empty_stmt(node) }
|
||||
ast.DebuggerStmt { return t.debugger_stmt(node) }
|
||||
}
|
||||
return t.null_node()
|
||||
}
|
||||
@ -1932,6 +1933,13 @@ fn (t Tree) empty_stmt(node ast.EmptyStmt) &Node {
|
||||
return obj
|
||||
}
|
||||
|
||||
fn (t Tree) debugger_stmt(node ast.DebuggerStmt) &Node {
|
||||
mut obj := new_object()
|
||||
obj.add_terse('ast_type', t.string_node('DebuggerStmt'))
|
||||
obj.add('pos', t.pos(node.pos))
|
||||
return obj
|
||||
}
|
||||
|
||||
fn (t Tree) nil_expr(node ast.Nil) &Node {
|
||||
mut obj := new_object()
|
||||
obj.add_terse('ast_type', t.string_node('Nil'))
|
||||
|
@ -1,6 +1,7 @@
|
||||
module builtin
|
||||
|
||||
fn print_backtrace_skipping_top_frames(xskipframes int) bool {
|
||||
// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames
|
||||
pub fn print_backtrace_skipping_top_frames(xskipframes int) bool {
|
||||
$if no_backtrace ? {
|
||||
return false
|
||||
} $else {
|
||||
|
@ -61,7 +61,8 @@ const symopt_include_32bit_modules = 0x00002000
|
||||
const symopt_allow_zero_address = 0x01000000
|
||||
const symopt_debug = u32(0x80000000)
|
||||
|
||||
fn print_backtrace_skipping_top_frames(skipframes int) bool {
|
||||
// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames
|
||||
pub fn print_backtrace_skipping_top_frames(skipframes int) bool {
|
||||
$if msvc {
|
||||
return print_backtrace_skipping_top_frames_msvc(skipframes)
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ pub type Stmt = AsmStmt
|
||||
| BranchStmt
|
||||
| ComptimeFor
|
||||
| ConstDecl
|
||||
| DebuggerStmt
|
||||
| DeferStmt
|
||||
| EmptyStmt
|
||||
| EnumDecl
|
||||
@ -1721,6 +1722,11 @@ pub const riscv_with_number_register_list = {
|
||||
'a#': 8
|
||||
}
|
||||
|
||||
pub struct DebuggerStmt {
|
||||
pub:
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// `assert a == 0, 'a is zero'`
|
||||
@[minify]
|
||||
pub struct AssertStmt {
|
||||
|
@ -182,6 +182,20 @@ pub fn (s &Scope) innermost(pos int) &Scope {
|
||||
return s
|
||||
}
|
||||
|
||||
// get_all_vars extracts all current scope vars
|
||||
pub fn (s &Scope) get_all_vars() []ScopeObject {
|
||||
mut scope_vars := []ScopeObject{}
|
||||
for sc := unsafe { s }; true; sc = sc.parent {
|
||||
if sc.objects.len > 0 {
|
||||
scope_vars << sc.objects.values().filter(|it| it is Var)
|
||||
}
|
||||
if sc.dont_lookup_parent() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return scope_vars
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (s &Scope) contains(pos int) bool {
|
||||
return pos >= s.start_pos && pos <= s.end_pos
|
||||
|
@ -2000,6 +2000,7 @@ fn (mut c Checker) stmt(mut node ast.Stmt) {
|
||||
}
|
||||
}
|
||||
ast.NodeError {}
|
||||
ast.DebuggerStmt {}
|
||||
ast.AsmStmt {
|
||||
c.asm_stmt(mut node)
|
||||
}
|
||||
|
284
vlib/v/debug/debug.v
Normal file
284
vlib/v/debug/debug.v
Normal file
@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2019-2024 Felipe Pena. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
|
||||
@[has_globals]
|
||||
module debug
|
||||
|
||||
import os
|
||||
import math
|
||||
import readline
|
||||
import strings
|
||||
|
||||
__global g_debugger = Debugger{}
|
||||
|
||||
// Debugger holds the V debug information for REPL
|
||||
@[heap]
|
||||
struct Debugger {
|
||||
mut:
|
||||
is_tty bool = os.is_atty(0) > 0 // is tty?
|
||||
exited bool // user exiting flag
|
||||
last_cmd string // save the last cmd
|
||||
last_args string // save the last args
|
||||
watch_vars []string // save the watched vars
|
||||
cmdline readline.Readline = readline.Readline{
|
||||
completion_list: [
|
||||
'anon?',
|
||||
'bt',
|
||||
'continue',
|
||||
'generic?',
|
||||
'heap',
|
||||
'help',
|
||||
'list',
|
||||
'mem',
|
||||
'memory',
|
||||
'method?',
|
||||
'mod',
|
||||
'print',
|
||||
'quit',
|
||||
'scope',
|
||||
'unwatch',
|
||||
'watch',
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// DebugContextVar holds the scope variable information
|
||||
pub struct DebugContextVar {
|
||||
name string // var name
|
||||
typ string // its type name
|
||||
value string // its str value
|
||||
}
|
||||
|
||||
// DebugContextInfo has the context info for the debugger repl
|
||||
pub struct DebugContextInfo {
|
||||
is_anon bool // cur fn is anon?
|
||||
is_generic bool // cur fn is a generic?
|
||||
is_method bool // cur fn is a bool?
|
||||
receiver_typ_name string // cur receiver type name (method only)
|
||||
line int // cur line number
|
||||
file string // cur file name
|
||||
mod string // cur module name
|
||||
fn_name string // cur function name
|
||||
scope map[string]DebugContextVar // scope var data
|
||||
}
|
||||
|
||||
fn flush_println(s string) {
|
||||
println(s)
|
||||
flush_stdout()
|
||||
}
|
||||
|
||||
// show_variable prints the variable info if found into the cur context
|
||||
fn (d DebugContextInfo) show_variable(var_name string, is_watch bool) {
|
||||
if info := d.scope[var_name] {
|
||||
flush_println('${var_name} = ${info.value} (${info.typ})')
|
||||
} else if !is_watch {
|
||||
eprintln('[error] var `${var_name}` not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn (d DebugContextInfo) show_watched_vars(watch_vars []string) {
|
||||
for var in watch_vars {
|
||||
d.show_variable(var, true)
|
||||
}
|
||||
}
|
||||
|
||||
// show_scope prints the cur context scope variables
|
||||
fn (d DebugContextInfo) show_scope() {
|
||||
for k, v in d.scope {
|
||||
flush_println('${k} = ${v.value} (${v.typ})')
|
||||
}
|
||||
}
|
||||
|
||||
// DebugContextInfo.ctx displays info about the current fn context
|
||||
fn (d DebugContextInfo) ctx() string {
|
||||
mut s := strings.new_builder(512)
|
||||
if d.is_method {
|
||||
s.write_string('[${d.mod}] (${d.receiver_typ_name}) ${d.fn_name}')
|
||||
} else {
|
||||
s.write_string('[${d.mod}] ${d.fn_name}')
|
||||
}
|
||||
return s.str()
|
||||
}
|
||||
|
||||
// print_help prints the debugger REPL commands help
|
||||
fn (mut d Debugger) print_help() {
|
||||
println('vdbg commands:')
|
||||
println(' anon?\t\t\tcheck if the current context is anon')
|
||||
println(' bt\t\t\tprints a backtrace')
|
||||
println(' c, continue\t\tcontinue debugging')
|
||||
println(' generic?\t\tcheck if the current context is generic')
|
||||
println(' heap\t\t\tshow heap memory usage')
|
||||
println(' h, help, ?\t\tshow this help')
|
||||
println(' l, list [lines]\tshow some lines from current break (default: 3)')
|
||||
println(' mem, memory\t\tshow memory usage')
|
||||
println(' method?\t\tcheck if the current context is a method')
|
||||
println(' m, mod\t\tshow current module name')
|
||||
println(' p, print <var>\tprints an variable')
|
||||
println(' q, quit\t\texits debugging session in the code')
|
||||
println(' scope\t\t\tshow the vars in the current scope')
|
||||
println(' u, unwatch <var>\tunwatches a variable')
|
||||
println(' w, watch <var>\twatches a variable')
|
||||
flush_println('')
|
||||
}
|
||||
|
||||
// read_line provides the user prompt based on tty flag
|
||||
fn (mut d Debugger) read_line(prompt string) (string, bool) {
|
||||
if d.is_tty {
|
||||
mut is_ctrl := false
|
||||
line := d.cmdline.read_line(prompt) or {
|
||||
is_ctrl = true
|
||||
''
|
||||
}
|
||||
return line.trim_right('\r\n '), is_ctrl
|
||||
} else {
|
||||
print(prompt)
|
||||
flush_stdout()
|
||||
return os.get_raw_line().trim_right('\r\n '), false
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut d Debugger) parse_input(input string, is_ctrl bool) (string, string) {
|
||||
splitted := input.split(' ')
|
||||
if !is_ctrl && splitted[0] == '' {
|
||||
return d.last_cmd, d.last_args
|
||||
} else {
|
||||
cmd := if is_ctrl { d.last_cmd } else { splitted[0] }
|
||||
args := if splitted.len > 1 { splitted[1] } else { '' }
|
||||
d.last_cmd = cmd
|
||||
d.last_args = args
|
||||
return cmd, args
|
||||
}
|
||||
}
|
||||
|
||||
// print_context_lines prints N lines before and after the current location
|
||||
fn (mut d Debugger) print_context_lines(path string, line int, lines int) ! {
|
||||
file_content := os.read_file(path)!
|
||||
chunks := file_content.split('\n')
|
||||
offset := math.max(line - lines, 1)
|
||||
for n, s in chunks[offset - 1..math.min(chunks.len, line + lines)] {
|
||||
ind := if n + offset == line { '>' } else { ' ' }
|
||||
flush_println('${n + offset:04}${ind} ${s}')
|
||||
}
|
||||
}
|
||||
|
||||
// print_memory_use prints the GC memory use
|
||||
fn (d &Debugger) print_memory_use() {
|
||||
flush_println(gc_memory_use().str())
|
||||
}
|
||||
|
||||
// print_heap_usage prints the GC heap usage
|
||||
fn (d &Debugger) print_heap_usage() {
|
||||
h := gc_heap_usage()
|
||||
flush_println('heap size: ${h.heap_size}')
|
||||
flush_println('free bytes: ${h.free_bytes}')
|
||||
flush_println('total bytes: ${h.total_bytes}')
|
||||
}
|
||||
|
||||
// watch_var adds a variable to watch_list
|
||||
fn (mut d Debugger) watch_var(var string) bool {
|
||||
if var !in d.watch_vars {
|
||||
d.watch_vars << var
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// unwatch_var removes a variable from watch list
|
||||
fn (mut d Debugger) unwatch_var(var string) {
|
||||
item := d.watch_vars.index(var)
|
||||
if item >= 0 {
|
||||
d.watch_vars.delete(item)
|
||||
}
|
||||
}
|
||||
|
||||
// interact displays the V debugger REPL for user interaction
|
||||
@[markused]
|
||||
pub fn (mut d Debugger) interact(info DebugContextInfo) ! {
|
||||
if d.exited {
|
||||
return
|
||||
}
|
||||
|
||||
flush_println('Break on ${info.ctx()} in ${info.file}:${info.line}')
|
||||
if d.watch_vars.len > 0 {
|
||||
info.show_watched_vars(d.watch_vars)
|
||||
}
|
||||
for {
|
||||
input, is_ctrl := d.read_line('${info.file}:${info.line} vdbg> ')
|
||||
cmd, args := d.parse_input(input, is_ctrl)
|
||||
match cmd {
|
||||
'anon?' {
|
||||
flush_println(info.is_anon.str())
|
||||
}
|
||||
'bt' {
|
||||
print_backtrace_skipping_top_frames(2)
|
||||
flush_stdout()
|
||||
}
|
||||
'c', 'continue' {
|
||||
break
|
||||
}
|
||||
'generic?' {
|
||||
flush_println(info.is_generic.str())
|
||||
}
|
||||
'heap' {
|
||||
d.print_heap_usage()
|
||||
}
|
||||
'?', 'h', 'help' {
|
||||
d.print_help()
|
||||
}
|
||||
'l', 'list' {
|
||||
lines := if args != '' { args.int() } else { 3 }
|
||||
if lines < 0 {
|
||||
eprintln('[error] cannot use negative line amount')
|
||||
} else {
|
||||
d.print_context_lines(info.file, info.line, lines)!
|
||||
}
|
||||
}
|
||||
'method?' {
|
||||
flush_println(info.is_method.str())
|
||||
}
|
||||
'mem', 'memory' {
|
||||
d.print_memory_use()
|
||||
}
|
||||
'm', 'mod' {
|
||||
flush_println(info.mod)
|
||||
}
|
||||
'p', 'print' {
|
||||
if args == '' {
|
||||
eprintln('[error] var name is expected as parameter')
|
||||
} else {
|
||||
info.show_variable(args, false)
|
||||
}
|
||||
}
|
||||
'scope' {
|
||||
info.show_scope()
|
||||
}
|
||||
'q', 'quit' {
|
||||
d.exited = true
|
||||
break
|
||||
}
|
||||
'u', 'unwatch' {
|
||||
if args == '' {
|
||||
eprintln('[error] var name is expected as parameter')
|
||||
} else {
|
||||
d.unwatch_var(args)
|
||||
}
|
||||
}
|
||||
'w', 'watch' {
|
||||
if args == '' {
|
||||
eprintln('[error] var name is expected as parameter')
|
||||
} else {
|
||||
if d.watch_var(args) {
|
||||
info.show_variable(args, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
'' {
|
||||
if is_ctrl {
|
||||
flush_println('')
|
||||
}
|
||||
}
|
||||
else {
|
||||
eprintln('unknown command `${cmd}`')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
vlib/v/debug/interactive_test.v
Normal file
59
vlib/v/debug/interactive_test.v
Normal file
@ -0,0 +1,59 @@
|
||||
import os
|
||||
import term
|
||||
import time
|
||||
|
||||
const vexe = @VEXE
|
||||
const expect_tests_path = os.join_path(@VEXEROOT, 'vlib', 'v', 'debug', 'tests')
|
||||
const test_module_path = os.join_path(os.vtmp_dir(), 'test_vdbg_input')
|
||||
const bar = term.yellow('-'.repeat(100))
|
||||
|
||||
const expect_exe = os.quoted_path(os.find_abs_path_of_executable('expect') or {
|
||||
eprintln('skipping test, since expect is missing')
|
||||
exit(0)
|
||||
})
|
||||
|
||||
fn testsuite_begin() {
|
||||
os.chdir(@VEXEROOT) or {}
|
||||
os.rmdir_all(test_module_path) or {}
|
||||
os.mkdir_all(test_module_path) or {}
|
||||
dump(test_module_path)
|
||||
}
|
||||
|
||||
fn gprintln(msg string) {
|
||||
println(term.gray(msg))
|
||||
}
|
||||
|
||||
fn test_debugger() {
|
||||
os.chdir(test_module_path)!
|
||||
all_expect_files := os.walk_ext(expect_tests_path, '.expect')
|
||||
assert all_expect_files.len > 0, 'no .expect files found in ${expect_tests_path}'
|
||||
for eidx, efile in all_expect_files {
|
||||
vfile := efile.replace('.expect', '.vv')
|
||||
output_file := os.join_path(test_module_path, os.file_name(efile).replace('.expect',
|
||||
'.exe'))
|
||||
|
||||
println(bar)
|
||||
gprintln('>>>> running test [${eidx + 1}/${all_expect_files.len}], ${term.magenta(efile)} ...')
|
||||
|
||||
compile_sw := time.new_stopwatch()
|
||||
comp_res := os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(output_file)} ${os.quoted_path(vfile)}')
|
||||
gprintln('>>>>>>>>>>> compilation took ${compile_sw.elapsed().milliseconds()} ms, comp_res: ${comp_res}')
|
||||
|
||||
expect_cmd := '${expect_exe} -d -c "set stty_init {rows 24 cols 80}" -c "set timeout 15" ${os.quoted_path(efile)} ${os.quoted_path(output_file)} ${os.quoted_path(vfile)}'
|
||||
println(term.cyan(expect_cmd))
|
||||
flush_stdout()
|
||||
sw := time.new_stopwatch()
|
||||
res := os.system(expect_cmd)
|
||||
gprintln('>>>>>>>>>>> expect took: ${sw.elapsed().milliseconds()} ms, res: ${res}')
|
||||
|
||||
if res != 0 {
|
||||
assert false, term.red('failed expect cmd: ${expect_cmd}')
|
||||
}
|
||||
assert true
|
||||
}
|
||||
os.chdir(@VEXEROOT) or {}
|
||||
os.rmdir_all(test_module_path) or {}
|
||||
|
||||
println(bar)
|
||||
gprintln(term.bold('A total of ${all_expect_files.len} tests for the debugger are OK'))
|
||||
}
|
9
vlib/v/debug/tests/aggregate.expect
Normal file
9
vlib/v/debug/tests/aggregate.expect
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "\[${test_file}:25\] a.a: 123\r\n"
|
||||
expect -ex "Break on \[main\] main in ${test_file}:26\r\n"
|
||||
expect -ex "${test_file}:26 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = Test{\r\n a: 123\r\n} ((main.main.Test | main.main.Test2))" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
30
vlib/v/debug/tests/aggregate.vv
Normal file
30
vlib/v/debug/tests/aggregate.vv
Normal file
@ -0,0 +1,30 @@
|
||||
struct Test {
|
||||
a int
|
||||
}
|
||||
|
||||
struct Test2 {
|
||||
a int
|
||||
}
|
||||
|
||||
struct Test3 {
|
||||
a int
|
||||
}
|
||||
|
||||
interface ITest {
|
||||
a int
|
||||
}
|
||||
|
||||
type TestSum = Test | Test2 | Test3
|
||||
|
||||
fn main() {
|
||||
a := TestSum(Test{
|
||||
a: 123
|
||||
})
|
||||
match a {
|
||||
Test, Test2 {
|
||||
dump(a.a)
|
||||
$dbg;
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
10
vlib/v/debug/tests/comptime_smartcast.expect
Normal file
10
vlib/v/debug/tests/comptime_smartcast.expect
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] comptime_smartcast in ${test_file}:3\r\n"
|
||||
expect "${test_file}:3 vdbg> " { send "p v\n" } timeout { exit 1 }
|
||||
expect "v = 1 (int)" { send "c\n" } timeout { exit 1 }
|
||||
expect "${test_file}:5 vdbg> " { send "p v\n" } timeout { exit 1 }
|
||||
expect "v = true (bool)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
12
vlib/v/debug/tests/comptime_smartcast.vv
Normal file
12
vlib/v/debug/tests/comptime_smartcast.vv
Normal file
@ -0,0 +1,12 @@
|
||||
fn comptime_smartcast[T](v T) {
|
||||
$if v is int {
|
||||
$dbg;
|
||||
} $else {
|
||||
$dbg;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
comptime_smartcast(1)
|
||||
comptime_smartcast(true)
|
||||
}
|
8
vlib/v/debug/tests/comptime_variant.expect
Normal file
8
vlib/v/debug/tests/comptime_variant.expect
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] comptime_variant_int in ${test_file}:7"
|
||||
expect -ex "${test_file}:7 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = 0 (int)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
15
vlib/v/debug/tests/comptime_variant.vv
Normal file
15
vlib/v/debug/tests/comptime_variant.vv
Normal file
@ -0,0 +1,15 @@
|
||||
type MySum = int | string
|
||||
|
||||
fn comptime_variant_int() {
|
||||
a := MySum(int(0))
|
||||
$for v in MySum.variants {
|
||||
if a is v {
|
||||
$dbg;
|
||||
dump(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
comptime_variant_int()
|
||||
}
|
8
vlib/v/debug/tests/interface_var.expect
Normal file
8
vlib/v/debug/tests/interface_var.expect
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] interface_var in ${test_file}:14\r\n"
|
||||
expect -ex "${test_file}:14 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = Test{\r\n a: MySum(true)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
22
vlib/v/debug/tests/interface_var.vv
Normal file
22
vlib/v/debug/tests/interface_var.vv
Normal file
@ -0,0 +1,22 @@
|
||||
interface ITest {
|
||||
a MySum
|
||||
}
|
||||
|
||||
struct Test {
|
||||
a MySum
|
||||
}
|
||||
|
||||
type MySum = bool | int
|
||||
|
||||
fn interface_var(a ITest) {
|
||||
match a {
|
||||
Test {
|
||||
$dbg;
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
interface_var(Test{ a: true })
|
||||
}
|
17
vlib/v/debug/tests/iteration.expect
Normal file
17
vlib/v/debug/tests/iteration.expect
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect "0"
|
||||
expect "1"
|
||||
expect "2"
|
||||
expect "4"
|
||||
expect "${test_file}:3 vdbg> " { send "p x\n" } timeout { exit 1 }
|
||||
expect "x = 5 (int literal)" { send "c\n"} timeout { exit 1 }
|
||||
expect "${test_file}:3 vdbg> " { send "p x\n" } timeout { exit 1 }
|
||||
expect "x = 6 (int literal)" { send "c\n"} timeout { exit 1 }
|
||||
expect "${test_file}:3 vdbg> " { send "q\n" } timeout { exit 1 }
|
||||
expect "7"
|
||||
expect "8"
|
||||
expect "9"
|
||||
expect eof
|
6
vlib/v/debug/tests/iteration.vv
Normal file
6
vlib/v/debug/tests/iteration.vv
Normal file
@ -0,0 +1,6 @@
|
||||
for x in 0 .. 10 {
|
||||
if x > 4 {
|
||||
$dbg;
|
||||
}
|
||||
println(x)
|
||||
}
|
8
vlib/v/debug/tests/mut_arg.expect
Normal file
8
vlib/v/debug/tests/mut_arg.expect
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] mut_arg in ${test_file}:10\r\n"
|
||||
expect "${test_file}:10 vdbg> " { send "p b\n" } timeout { exit 1 }
|
||||
expect "b = foo (&main.Test)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
18
vlib/v/debug/tests/mut_arg.vv
Normal file
18
vlib/v/debug/tests/mut_arg.vv
Normal file
@ -0,0 +1,18 @@
|
||||
struct Test {
|
||||
a string
|
||||
}
|
||||
|
||||
fn (t &Test) str() string {
|
||||
return t.a
|
||||
}
|
||||
|
||||
fn test_mut(mut b Test) {
|
||||
$dbg;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut a := Test{
|
||||
a: 'foo'
|
||||
}
|
||||
test_mut(mut a)
|
||||
}
|
9
vlib/v/debug/tests/option_unwrap.expect
Normal file
9
vlib/v/debug/tests/option_unwrap.expect
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] option_unwrap in ${test_file}:4\r\n"
|
||||
expect -ex "${test_file}:4 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = Option(123) (?int)" { send "p b\n" } timeout { exit 1 }
|
||||
expect -ex "b = 123 (int)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
10
vlib/v/debug/tests/option_unwrap.vv
Normal file
10
vlib/v/debug/tests/option_unwrap.vv
Normal file
@ -0,0 +1,10 @@
|
||||
fn option_unwrap() ? {
|
||||
a := ?int(123)
|
||||
b := a?
|
||||
$dbg;
|
||||
dump(b)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
option_unwrap()?
|
||||
}
|
8
vlib/v/debug/tests/smartcast.expect
Normal file
8
vlib/v/debug/tests/smartcast.expect
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] smartcast in ${test_file}:3"
|
||||
expect -ex "${test_file}:3 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = 1 (int)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
9
vlib/v/debug/tests/smartcast.vv
Normal file
9
vlib/v/debug/tests/smartcast.vv
Normal file
@ -0,0 +1,9 @@
|
||||
fn smartcast(a ?int) {
|
||||
if a != none {
|
||||
$dbg;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
smartcast(1)
|
||||
}
|
10
vlib/v/debug/tests/sumtype.expect
Normal file
10
vlib/v/debug/tests/sumtype.expect
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env expect
|
||||
spawn [lindex $argv 0]
|
||||
set test_file [lindex $argv 1]
|
||||
|
||||
expect -ex "Break on \[main\] sumtype in ${test_file}:17"
|
||||
expect -ex "${test_file}:17 vdbg> " { send "p a\n" } timeout { exit 1 }
|
||||
expect -ex "a = Test{\r\n a: MySum(false)\r\n} (main.Test)" { send "c\n" } timeout { exit 1 }
|
||||
expect -ex "${test_file}:25 vdbg> " { send "p b\n" } timeout { exit 1 }
|
||||
expect -ex "b = Test{\r\n a: MySum(1)\r\n} (main.Test)" { send "q\n" } timeout { exit 1 }
|
||||
expect eof
|
31
vlib/v/debug/tests/sumtype.vv
Normal file
31
vlib/v/debug/tests/sumtype.vv
Normal file
@ -0,0 +1,31 @@
|
||||
struct Test {
|
||||
a MySum
|
||||
}
|
||||
|
||||
struct Test2 {
|
||||
a ?MySum
|
||||
}
|
||||
|
||||
type MySum = Test | bool | int
|
||||
|
||||
fn sumtype() {
|
||||
a := MySum(Test{
|
||||
a: false
|
||||
})
|
||||
if a is Test {
|
||||
dump(a)
|
||||
$dbg;
|
||||
}
|
||||
|
||||
b := ?MySum(Test{
|
||||
a: 1
|
||||
})
|
||||
if b is Test {
|
||||
dump(b)
|
||||
$dbg;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
sumtype()
|
||||
}
|
@ -513,6 +513,9 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) {
|
||||
ast.ConstDecl {
|
||||
f.const_decl(node)
|
||||
}
|
||||
ast.DebuggerStmt {
|
||||
f.debugger_stmt(node)
|
||||
}
|
||||
ast.DeferStmt {
|
||||
f.defer_stmt(node)
|
||||
}
|
||||
@ -874,6 +877,10 @@ pub fn (mut f Fmt) block(node ast.Block) {
|
||||
f.writeln('}')
|
||||
}
|
||||
|
||||
pub fn (mut f Fmt) debugger_stmt(node ast.DebuggerStmt) {
|
||||
f.writeln('\$dbg;')
|
||||
}
|
||||
|
||||
pub fn (mut f Fmt) branch_stmt(node ast.BranchStmt) {
|
||||
f.writeln(node.str())
|
||||
}
|
||||
|
@ -240,10 +240,10 @@ mut:
|
||||
/////////
|
||||
// out_parallel []strings.Builder
|
||||
// out_idx int
|
||||
out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder
|
||||
static_modifier string // for parallel_cc
|
||||
|
||||
has_reflection bool
|
||||
out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder
|
||||
static_modifier string // for parallel_cc
|
||||
has_reflection bool // v.reflection has been imported
|
||||
has_debugger bool // $dbg has been used in the code
|
||||
reflection_strings &map[string]int
|
||||
defer_return_tmp_var string
|
||||
vweb_filter_fn_name string // vweb__filter or x__vweb__filter, used by $vweb.html() for escaping strings in the templates, depending on which `vweb` import is used
|
||||
@ -319,6 +319,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref_ &pref.Preferences) (string
|
||||
|| pref_.os in [.wasm32, .wasm32_emscripten])
|
||||
static_modifier: if pref_.parallel_cc { 'static' } else { '' }
|
||||
has_reflection: 'v.reflection' in table.modules
|
||||
has_debugger: 'v.debug' in table.modules
|
||||
reflection_strings: &reflection_strings
|
||||
}
|
||||
|
||||
@ -687,6 +688,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen {
|
||||
is_cc_msvc: global_g.is_cc_msvc
|
||||
use_segfault_handler: global_g.use_segfault_handler
|
||||
has_reflection: 'v.reflection' in global_g.table.modules
|
||||
has_debugger: 'v.debug' in global_g.table.modules
|
||||
reflection_strings: global_g.reflection_strings
|
||||
}
|
||||
g.comptime = &comptime.ComptimeInfo{
|
||||
@ -2071,6 +2073,9 @@ fn (mut g Gen) stmt(node ast.Stmt) {
|
||||
ast.ComptimeFor {
|
||||
g.comptime_for(node)
|
||||
}
|
||||
ast.DebuggerStmt {
|
||||
g.debugger_stmt(node)
|
||||
}
|
||||
ast.DeferStmt {
|
||||
mut defer_stmt := node
|
||||
defer_stmt.ifdef = g.defer_ifdef
|
||||
@ -3910,6 +3915,133 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
|
||||
}
|
||||
}
|
||||
|
||||
// debugger_stmt writes the call to V debugger REPL
|
||||
fn (mut g Gen) debugger_stmt(node ast.DebuggerStmt) {
|
||||
paline, pafile, pamod, pafn := g.panic_debug_info(node.pos)
|
||||
is_anon := g.cur_fn != unsafe { nil } && g.cur_fn.is_anon
|
||||
is_generic := g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0
|
||||
is_method := g.cur_fn != unsafe { nil } && g.cur_fn.is_method
|
||||
receiver_type := if g.cur_fn != unsafe { nil } && g.cur_fn.is_method {
|
||||
g.table.type_to_str(g.cur_fn.receiver.typ)
|
||||
} else {
|
||||
''
|
||||
}
|
||||
scope_vars := g.file.scope.innermost(node.pos.pos).get_all_vars()
|
||||
|
||||
// prepares the map containing the scope variable infos
|
||||
mut vars := []string{}
|
||||
mut keys := strings.new_builder(100)
|
||||
mut values := strings.new_builder(100)
|
||||
mut count := 1
|
||||
for _, obj in scope_vars {
|
||||
if obj.name !in vars {
|
||||
if obj is ast.Var && obj.pos.pos < node.pos.pos {
|
||||
keys.write_string('_SLIT("${obj.name}")')
|
||||
var_typ := if obj.smartcasts.len > 0 { obj.smartcasts.last() } else { obj.typ }
|
||||
values.write_string('{.typ=_SLIT("${g.table.type_to_str(g.unwrap_generic(var_typ))}"),.value=')
|
||||
obj_sym := g.table.sym(obj.typ)
|
||||
cast_sym := g.table.sym(var_typ)
|
||||
|
||||
mut param_var := strings.new_builder(50)
|
||||
if obj.smartcasts.len > 0 {
|
||||
is_option_unwrap := obj.typ.has_flag(.option)
|
||||
&& var_typ == obj.typ.clear_flag(.option)
|
||||
is_option := obj.typ.has_flag(.option)
|
||||
mut opt_cast := false
|
||||
mut func := if cast_sym.info is ast.Aggregate {
|
||||
''
|
||||
} else {
|
||||
g.get_str_fn(var_typ)
|
||||
}
|
||||
|
||||
param_var.write_string('(')
|
||||
if obj_sym.kind == .sum_type && !obj.is_auto_heap {
|
||||
if is_option {
|
||||
if !is_option_unwrap {
|
||||
param_var.write_string('*(')
|
||||
}
|
||||
styp := g.base_type(obj.typ)
|
||||
param_var.write_string('*(${styp}*)')
|
||||
opt_cast = true
|
||||
} else {
|
||||
param_var.write_string('*')
|
||||
}
|
||||
} else if g.table.is_interface_var(obj) || obj.ct_type_var == .smartcast {
|
||||
param_var.write_string('*')
|
||||
} else if is_option {
|
||||
opt_cast = true
|
||||
param_var.write_string('*(${g.base_type(obj.typ)}*)')
|
||||
}
|
||||
|
||||
dot := if obj.orig_type.is_ptr() || obj.is_auto_heap { '->' } else { '.' }
|
||||
if obj.ct_type_var == .smartcast {
|
||||
cur_variant_sym := g.table.sym(g.unwrap_generic(g.comptime.type_map['${g.comptime.comptime_for_variant_var}.typ']))
|
||||
param_var.write_string('${obj.name}${dot}_${cur_variant_sym.cname}')
|
||||
} else if cast_sym.info is ast.Aggregate {
|
||||
sym := g.table.sym(cast_sym.info.types[g.aggregate_type_idx])
|
||||
func = g.get_str_fn(cast_sym.info.types[g.aggregate_type_idx])
|
||||
param_var.write_string('${obj.name}${dot}_${sym.cname}')
|
||||
} else if obj_sym.kind == .interface_ && cast_sym.kind == .interface_ {
|
||||
ptr := '*'.repeat(obj.typ.nr_muls())
|
||||
param_var.write_string('I_${obj_sym.cname}_as_I_${cast_sym.cname}(${ptr}${obj.name})')
|
||||
} else if obj_sym.kind in [.sum_type, .interface_] {
|
||||
param_var.write_string('${obj.name}')
|
||||
if opt_cast {
|
||||
param_var.write_string('.data)')
|
||||
}
|
||||
param_var.write_string('${dot}_${cast_sym.cname}')
|
||||
} else if obj.typ.has_flag(.option) && !var_typ.has_flag(.option) {
|
||||
param_var.write_string('${obj.name}.data')
|
||||
} else {
|
||||
param_var.write_string('${obj.name}')
|
||||
}
|
||||
param_var.write_string(')')
|
||||
|
||||
values.write_string('${func}(${param_var.str()})}')
|
||||
} else {
|
||||
func := g.get_str_fn(var_typ)
|
||||
if obj.typ.has_flag(.option) && !var_typ.has_flag(.option) {
|
||||
// option unwrap
|
||||
base_typ := g.base_type(obj.typ)
|
||||
values.write_string('${func}(*(${base_typ}*)${obj.name}.data)}')
|
||||
} else {
|
||||
_, str_method_expects_ptr, _ := cast_sym.str_method_info()
|
||||
deref := if str_method_expects_ptr && !obj.typ.is_ptr() {
|
||||
'&'
|
||||
} else if obj.typ.is_ptr() && !obj.is_auto_deref {
|
||||
'&'
|
||||
} else if obj.typ.is_ptr() && obj.is_auto_deref {
|
||||
''
|
||||
} else if obj.is_auto_heap
|
||||
|| (!var_typ.has_flag(.option) && var_typ.is_ptr()) {
|
||||
'*'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
values.write_string('${func}(${deref}${obj.name})}')
|
||||
}
|
||||
}
|
||||
vars << obj.name
|
||||
if count != scope_vars.len {
|
||||
keys.write_string(',')
|
||||
values.write_string(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
g.writeln('{')
|
||||
g.writeln('\tMap_string_string _scope = new_map_init(&map_hash_string, &map_eq_string, &map_clone_string, &map_free_string, ${vars.len}, sizeof(string), sizeof(v__debug__DebugContextVar),')
|
||||
g.write('\t\t_MOV((string[${vars.len}]){')
|
||||
g.write(keys.str())
|
||||
g.writeln('}),')
|
||||
g.write('\t\t_MOV((v__debug__DebugContextVar[${vars.len}]){')
|
||||
g.write(values.str())
|
||||
g.writeln('}));')
|
||||
g.writeln('\tv__debug__Debugger_interact(&g_debugger, (v__debug__DebugContextInfo){.is_anon=${is_anon},.is_generic=${is_generic},.is_method=${is_method},.receiver_typ_name=_SLIT("${receiver_type}"),.line=${paline},.file=_SLIT("${pafile}"),.mod=_SLIT("${pamod}"),.fn_name=_SLIT("${pafn}"),.scope=_scope});')
|
||||
g.write('}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) enum_decl(node ast.EnumDecl) {
|
||||
enum_name := util.no_dots(node.name)
|
||||
is_flag := node.is_flag
|
||||
@ -5948,12 +6080,11 @@ fn (mut g Gen) write_init_function() {
|
||||
g.gen_reflection_data()
|
||||
}
|
||||
}
|
||||
|
||||
mut cleaning_up_array := []string{cap: g.table.modules.len}
|
||||
|
||||
for mod_name in g.table.modules {
|
||||
if g.has_reflection && mod_name == 'v.reflection' {
|
||||
// ignore v.reflection already initialized above
|
||||
// ignore v.reflection and v.debug already initialized above
|
||||
continue
|
||||
}
|
||||
mut const_section_header_shown := false
|
||||
|
@ -425,6 +425,7 @@ pub fn (mut f Gen) stmt(node ast.Stmt) {
|
||||
ast.ConstDecl {
|
||||
f.const_decl(node)
|
||||
}
|
||||
ast.DebuggerStmt {}
|
||||
ast.DeferStmt {
|
||||
f.defer_stmt(node)
|
||||
}
|
||||
|
@ -668,6 +668,7 @@ fn (mut g JsGen) stmt_no_semi(node_ ast.Stmt) {
|
||||
g.write_v_source_line_info(node.pos)
|
||||
g.gen_const_decl(node)
|
||||
}
|
||||
ast.DebuggerStmt {}
|
||||
ast.DeferStmt {
|
||||
g.defer_stmts << node
|
||||
}
|
||||
@ -775,6 +776,7 @@ fn (mut g JsGen) stmt(node_ ast.Stmt) {
|
||||
g.write_v_source_line_info(node.pos)
|
||||
g.gen_const_decl(node)
|
||||
}
|
||||
ast.DebuggerStmt {}
|
||||
ast.DeferStmt {
|
||||
g.defer_stmts << node
|
||||
}
|
||||
|
@ -101,6 +101,7 @@ pub fn (mut w Walker) stmt(node_ ast.Stmt) {
|
||||
mut node := unsafe { node_ }
|
||||
match mut node {
|
||||
ast.EmptyStmt {}
|
||||
ast.DebuggerStmt {}
|
||||
ast.AsmStmt {
|
||||
w.asm_io(node.output)
|
||||
w.asm_io(node.input)
|
||||
|
@ -1058,12 +1058,17 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
|
||||
return p.comptime_for()
|
||||
}
|
||||
.name {
|
||||
mut pos := p.tok.pos()
|
||||
expr := p.expr(0)
|
||||
pos.update_last_line(p.prev_tok.line_nr)
|
||||
return ast.ExprStmt{
|
||||
expr: expr
|
||||
pos: pos
|
||||
// handles $dbg directly without registering token
|
||||
if p.peek_tok.lit == 'dbg' {
|
||||
return p.dbg_stmt()
|
||||
} else {
|
||||
mut pos := p.tok.pos()
|
||||
expr := p.expr(0)
|
||||
pos.update_last_line(p.prev_tok.line_nr)
|
||||
return ast.ExprStmt{
|
||||
expr: expr
|
||||
pos: pos
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1149,6 +1154,16 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Parser) dbg_stmt() ast.DebuggerStmt {
|
||||
pos := p.tok.pos()
|
||||
p.check(.dollar)
|
||||
p.check(.name)
|
||||
p.register_auto_import('v.debug')
|
||||
return ast.DebuggerStmt{
|
||||
pos: pos
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Parser) semicolon_stmt() ast.SemicolonStmt {
|
||||
pos := p.tok.pos()
|
||||
p.check(.semicolon)
|
||||
|
@ -178,6 +178,7 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) ast.Stmt {
|
||||
ast.EmptyStmt {}
|
||||
ast.NodeError {}
|
||||
ast.AsmStmt {}
|
||||
ast.DebuggerStmt {}
|
||||
ast.AssertStmt {
|
||||
return t.assert_stmt(mut node)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user