v: $dbg statement - native V debugger REPL (#20533)

This commit is contained in:
Felipe Pena 2024-01-19 02:10:17 -03:00 committed by GitHub
parent 45e13ea02a
commit 9f6448e30e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 786 additions and 14 deletions

View File

@ -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'))

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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
View 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}`')
}
}
}
}

View 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'))
}

View 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

View 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 {}
}
}

View 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

View 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)
}

View 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

View 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()
}

View 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

View 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 })
}

View 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

View File

@ -0,0 +1,6 @@
for x in 0 .. 10 {
if x > 4 {
$dbg;
}
println(x)
}

View 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

View 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)
}

View 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

View File

@ -0,0 +1,10 @@
fn option_unwrap() ? {
a := ?int(123)
b := a?
$dbg;
dump(b)
}
fn main() {
option_unwrap()?
}

View 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

View File

@ -0,0 +1,9 @@
fn smartcast(a ?int) {
if a != none {
$dbg;
}
}
fn main() {
smartcast(1)
}

View 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

View 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()
}

View File

@ -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())
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)
}