v2 / vlib / v / debug / debug.v
290 lines · 270 sloc · 7.36 KB · 0e0408f9b22153b87ace466493ef2080514a1a7b
Raw
1// Copyright (c) 2019-2024 Felipe Pena. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3@[has_globals]
4module debug
5
6import os
7import readline
8import strings
9import term
10
11@[markused]
12__global g_debugger = Debugger{}
13
14// Debugger holds the V debug information for REPL
15@[heap]
16struct Debugger {
17mut:
18 is_tty bool = os.is_atty(0) > 0 // is tty?
19 exited bool // user exiting flag
20 last_cmd string // save the last cmd
21 last_args string // save the last args
22 watch_vars []string // save the watched vars
23 cmdline readline.Readline = readline.Readline{
24 completion_list: [
25 'anon?',
26 'bt',
27 'clear',
28 'continue',
29 'generic?',
30 'heap',
31 'help',
32 'list',
33 'mem',
34 'memory',
35 'method?',
36 'mod',
37 'print',
38 'quit',
39 'scope',
40 'unwatch',
41 'watch',
42 ]
43 }
44}
45
46// DebugContextVar holds the scope variable information
47pub struct DebugContextVar {
48 name string // var name
49 typ string // its type name
50 value string // its str value
51}
52
53// DebugContextInfo has the context info for the debugger repl
54pub struct DebugContextInfo {
55 is_anon bool // cur fn is anon?
56 is_generic bool // cur fn is a generic?
57 is_method bool // cur fn is a bool?
58 receiver_typ_name string // cur receiver type name (method only)
59 line int // cur line number
60 file string // cur file name
61 mod string // cur module name
62 fn_name string // cur function name
63 scope map[string]DebugContextVar // scope var data
64}
65
66fn flush_println(s string) {
67 println(s)
68 flush_stdout()
69}
70
71// show_variable prints the variable info if found into the cur context
72fn (d DebugContextInfo) show_variable(var_name string, is_watch bool) {
73 if info := d.scope[var_name] {
74 flush_println('${var_name} = ${info.value} (${info.typ})')
75 } else if !is_watch {
76 eprintln('[error] var `${var_name}` not found')
77 }
78}
79
80fn (d DebugContextInfo) show_watched_vars(watch_vars []string) {
81 for var in watch_vars {
82 d.show_variable(var, true)
83 }
84}
85
86// show_scope prints the cur context scope variables
87fn (d DebugContextInfo) show_scope() {
88 for k, v in d.scope {
89 flush_println('${k} = ${v.value} (${v.typ})')
90 }
91}
92
93// DebugContextInfo.ctx displays info about the current fn context
94fn (d DebugContextInfo) ctx() string {
95 mut s := strings.new_builder(512)
96 if d.is_method {
97 s.write_string('[${d.mod}] (${d.receiver_typ_name}) ${d.fn_name}')
98 } else {
99 s.write_string('[${d.mod}] ${d.fn_name}')
100 }
101 return s.str()
102}
103
104// print_help prints the debugger REPL commands help
105fn (mut d Debugger) print_help() {
106 println('vdbg commands:')
107 println(' anon?\t\t\tcheck if the current context is anon')
108 println(' bt\t\t\tprints a backtrace')
109 println(' c, continue\t\tcontinue debugging')
110 println(' generic?\t\tcheck if the current context is generic')
111 println(' heap\t\t\tshow heap memory usage')
112 println(' h, help, ?\t\tshow this help')
113 println(' l, list [lines]\tshow some lines from current break (default: 3)')
114 println(' mem, memory\t\tshow memory usage')
115 println(' method?\t\tcheck if the current context is a method')
116 println(' m, mod\t\tshow current module name')
117 println(' p, print <var>\tprints an variable')
118 println(' q, quit\t\texits debugging session in the code')
119 println(' scope\t\t\tshow the vars in the current scope')
120 println(' u, unwatch <var>\tunwatches a variable')
121 println(' w, watch <var>\twatches a variable')
122 println(' clear\t\t\tclears the terminal window')
123 flush_println('')
124}
125
126// read_line provides the user prompt based on tty flag
127fn (mut d Debugger) read_line(prompt string) (string, bool) {
128 if d.is_tty {
129 mut is_ctrl := false
130 line := d.cmdline.read_line(prompt) or {
131 is_ctrl = true
132 ''
133 }
134 return line.trim_right('\r\n '), is_ctrl
135 } else {
136 print(prompt)
137 flush_stdout()
138 return os.get_raw_line().trim_right('\r\n '), false
139 }
140}
141
142fn (mut d Debugger) parse_input(input string, is_ctrl bool) (string, string) {
143 splitted := input.split(' ')
144 if !is_ctrl && splitted[0] == '' {
145 return d.last_cmd, d.last_args
146 } else {
147 cmd := if is_ctrl { d.last_cmd } else { splitted[0] }
148 args := if splitted.len > 1 { splitted[1] } else { '' }
149 d.last_cmd = cmd
150 d.last_args = args
151 return cmd, args
152 }
153}
154
155// print_context_lines prints N lines before and after the current location
156fn (mut d Debugger) print_context_lines(path string, line int, lines int) ! {
157 file_content := os.read_file(path)!
158 chunks := file_content.split('\n')
159 offset := int_max(line - lines, 1)
160 for n, s in chunks[offset - 1..int_min(chunks.len, line + lines)] {
161 ind := if n + offset == line { '>' } else { ' ' }
162 flush_println('${n + offset:04}${ind} ${s}')
163 }
164}
165
166// print_memory_use prints the GC memory use
167fn (d &Debugger) print_memory_use() {
168 flush_println(gc_memory_use().str())
169}
170
171// print_heap_usage prints the GC heap usage
172fn (d &Debugger) print_heap_usage() {
173 h := gc_heap_usage()
174 flush_println('heap size: ${h.heap_size}')
175 flush_println('free bytes: ${h.free_bytes}')
176 flush_println('total bytes: ${h.total_bytes}')
177}
178
179// watch_var adds a variable to watch_list
180fn (mut d Debugger) watch_var(var string) bool {
181 if var !in d.watch_vars {
182 d.watch_vars << var
183 return true
184 }
185 return false
186}
187
188// unwatch_var removes a variable from watch list
189fn (mut d Debugger) unwatch_var(var string) {
190 item := d.watch_vars.index(var)
191 if item >= 0 {
192 d.watch_vars.delete(item)
193 }
194}
195
196// interact displays the V debugger REPL for user interaction
197@[markused]
198pub fn (mut d Debugger) interact(info DebugContextInfo) ! {
199 if d.exited {
200 return
201 }
202
203 flush_println('Break on ${info.ctx()} in ${info.file}:${info.line}')
204 if d.watch_vars.len > 0 {
205 info.show_watched_vars(d.watch_vars)
206 }
207 for {
208 input, is_ctrl := d.read_line('${info.file}:${info.line} vdbg> ')
209 cmd, args := d.parse_input(input, is_ctrl)
210 match cmd {
211 'anon?' {
212 flush_println(info.is_anon.str())
213 }
214 'bt' {
215 print_backtrace_skipping_top_frames(2)
216 flush_stdout()
217 }
218 'c', 'continue' {
219 break
220 }
221 'generic?' {
222 flush_println(info.is_generic.str())
223 }
224 'heap' {
225 d.print_heap_usage()
226 }
227 '?', 'h', 'help' {
228 d.print_help()
229 }
230 'l', 'list' {
231 lines := if args != '' { args.int() } else { 3 }
232 if lines < 0 {
233 eprintln('[error] cannot use negative line amount')
234 } else {
235 d.print_context_lines(info.file, info.line, lines)!
236 }
237 }
238 'method?' {
239 flush_println(info.is_method.str())
240 }
241 'mem', 'memory' {
242 d.print_memory_use()
243 }
244 'm', 'mod' {
245 flush_println(info.mod)
246 }
247 'p', 'print' {
248 if args == '' {
249 eprintln('[error] var name is expected as parameter')
250 } else {
251 info.show_variable(args, false)
252 }
253 }
254 'scope' {
255 info.show_scope()
256 }
257 'q', 'quit' {
258 d.exited = true
259 break
260 }
261 'u', 'unwatch' {
262 if args == '' {
263 eprintln('[error] var name is expected as parameter')
264 } else {
265 d.unwatch_var(args)
266 }
267 }
268 'w', 'watch' {
269 if args == '' {
270 eprintln('[error] var name is expected as parameter')
271 } else {
272 if d.watch_var(args) {
273 info.show_variable(args, false)
274 }
275 }
276 }
277 'clear' {
278 term.erase_clear()
279 }
280 '' {
281 if is_ctrl {
282 flush_println('')
283 }
284 }
285 else {
286 eprintln('unknown command `${cmd}`')
287 }
288 }
289 }
290}
291