cgen: print actual values on a failed assert (when possible)
parent
39bd058acf
commit
945439dab6
|
@ -9,22 +9,26 @@ import os
|
||||||
// / customizing the look & feel of the assertions results easier,
|
// / customizing the look & feel of the assertions results easier,
|
||||||
// / since it is done in normal V code, instead of in embedded C ...
|
// / since it is done in normal V code, instead of in embedded C ...
|
||||||
// //////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////
|
||||||
fn cb_assertion_failed(filename string, line int, sourceline string, funcname string) {
|
fn cb_assertion_failed(i &VAssertMetaInfo) {
|
||||||
// color_on := term.can_show_color_on_stderr()
|
// color_on := term.can_show_color_on_stderr()
|
||||||
use_relative_paths := match os.getenv('VERROR_PATHS') {
|
use_relative_paths := match os.getenv('VERROR_PATHS') {
|
||||||
'absolute'{
|
'absolute' {
|
||||||
false
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
true}
|
|
||||||
}
|
}
|
||||||
final_filename := if use_relative_paths { filename } else { os.real_path(filename) }
|
final_filename := if use_relative_paths { i.fpath } else { os.real_path(i.fpath) }
|
||||||
final_funcname := funcname.replace('main__', '').replace('__', '.')
|
final_funcname := i.fn_name.replace('main__', '').replace('__', '.')
|
||||||
eprintln('$final_filename:$line: failed assert in ${final_funcname}')
|
eprintln('$final_filename:${i.line_nr+1}: failed assert in ${final_funcname}')
|
||||||
eprintln('Source : $sourceline')
|
eprintln('Source : ${i.src}')
|
||||||
|
if i.op != 'call' {
|
||||||
|
eprintln(' left value: ${i.llabel} = ${i.lvalue}')
|
||||||
|
eprintln(' right value: ${i.rlabel} = ${i.rvalue}')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cb_assertion_ok(filename string, line int, sourceline string, funcname string) {
|
fn cb_assertion_ok(i &VAssertMetaInfo) {
|
||||||
// do nothing for now on an OK assertion
|
// do nothing for now on an OK assertion
|
||||||
// println('OK ${line:5d}|$sourceline ')
|
// println('OK ${(i.line_nr+1):5d}|${i.src}')
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,3 +220,25 @@ fn __as_cast(obj voidptr, obj_type, expected_type int) voidptr {
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VAssertMetaInfo is used during assertions. An instance of it
|
||||||
|
// is filled in by compile time generated code, when an assertion fails.
|
||||||
|
struct VAssertMetaInfo {
|
||||||
|
pub:
|
||||||
|
fpath string // the source file path of the assertion
|
||||||
|
line_nr int // the line number of the assertion
|
||||||
|
fn_name string // the function name in which the assertion is
|
||||||
|
src string // the actual source line of the assertion
|
||||||
|
op string // the operation of the assertion, i.e. '==', '<', 'call', etc ...
|
||||||
|
llabel string // the left side of the infix expressions as source
|
||||||
|
rlabel string // the right side of the infix expressions as source
|
||||||
|
lvalue string // the stringified *actual value* of the left side of a failed assertion
|
||||||
|
rvalue string // the stringified *actual value* of the right side of a failed assertion
|
||||||
|
}
|
||||||
|
fn __print_assert_failure(i &VAssertMetaInfo) {
|
||||||
|
eprintln('${i.fpath}:${i.line_nr+1}: FAIL: fn ${i.fn_name}: assert ${i.src}')
|
||||||
|
if i.op != 'call' {
|
||||||
|
eprintln(' left value: ${i.llabel} = ${i.lvalue}')
|
||||||
|
eprintln(' right value: ${i.rlabel} = ${i.rvalue}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -85,6 +85,10 @@ pub fn (node &FnDecl) str(t &table.Table) string {
|
||||||
return f.str()
|
return f.str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn (x &InfixExpr) str() string {
|
||||||
|
return '${x.left.str()} $x.op.str() ${x.right.str()}'
|
||||||
|
}
|
||||||
|
|
||||||
// string representaiton of expr
|
// string representaiton of expr
|
||||||
pub fn (x Expr) str() string {
|
pub fn (x Expr) str() string {
|
||||||
match x {
|
match x {
|
||||||
|
|
|
@ -897,6 +897,14 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type, expected_type table.Type)
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cestring returns a V string, properly escaped for embeddeding in a C string literal.
|
||||||
|
fn cestring(s string) string {
|
||||||
|
return s.replace('\\', '\\\\').replace('"', "\'")
|
||||||
|
}
|
||||||
|
// ctoslit returns a 'tos_lit("$s")' call, where s is properly escaped.
|
||||||
|
fn ctoslit(s string) string {
|
||||||
|
return 'tos_lit("' + cestring(s) + '")'
|
||||||
|
}
|
||||||
fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
|
fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
|
||||||
g.writeln('// assert')
|
g.writeln('// assert')
|
||||||
g.inside_ternary++
|
g.inside_ternary++
|
||||||
|
@ -904,18 +912,15 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
|
||||||
g.expr(a.expr)
|
g.expr(a.expr)
|
||||||
g.write(')')
|
g.write(')')
|
||||||
g.decrement_inside_ternary()
|
g.decrement_inside_ternary()
|
||||||
s_assertion := a.expr.str().replace('"', "\'")
|
|
||||||
mut mod_path := g.file.path
|
|
||||||
$if windows {
|
|
||||||
mod_path = g.file.path.replace('\\', '\\\\')
|
|
||||||
}
|
|
||||||
if g.is_test {
|
if g.is_test {
|
||||||
g.writeln('{')
|
g.writeln('{')
|
||||||
g.writeln(' g_test_oks++;')
|
g.writeln(' g_test_oks++;')
|
||||||
g.writeln(' cb_assertion_ok( tos_lit("${mod_path}"), ${a.pos.line_nr+1}, tos_lit("assert ${s_assertion}"), tos_lit("${g.fn_decl.name}()") );')
|
metaname_ok := g.gen_assert_metainfo(a)
|
||||||
|
g.writeln(' cb_assertion_ok(&${metaname_ok});')
|
||||||
g.writeln('}else{')
|
g.writeln('}else{')
|
||||||
g.writeln(' g_test_fails++;')
|
g.writeln(' g_test_fails++;')
|
||||||
g.writeln(' cb_assertion_failed( tos_lit("${mod_path}"), ${a.pos.line_nr+1}, tos_lit("assert ${s_assertion}"), tos_lit("${g.fn_decl.name}()") );')
|
metaname_fail := g.gen_assert_metainfo(a)
|
||||||
|
g.writeln(' cb_assertion_failed(&${metaname_fail});')
|
||||||
g.writeln(' exit(1);')
|
g.writeln(' exit(1);')
|
||||||
g.writeln(' // TODO')
|
g.writeln(' // TODO')
|
||||||
g.writeln(' // Maybe print all vars in a test function if it fails?')
|
g.writeln(' // Maybe print all vars in a test function if it fails?')
|
||||||
|
@ -923,12 +928,59 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.writeln('{}else{')
|
g.writeln('{}else{')
|
||||||
g.writeln(' eprintln( tos_lit("${mod_path}:${a.pos.line_nr+1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));')
|
metaname_panic := g.gen_assert_metainfo(a)
|
||||||
|
g.writeln(' __print_assert_failure(&${metaname_panic});')
|
||||||
g.writeln(' v_panic(tos_lit("Assertion failed..."));')
|
g.writeln(' v_panic(tos_lit("Assertion failed..."));')
|
||||||
g.writeln(' exit(1);')
|
g.writeln(' exit(1);')
|
||||||
g.writeln('}')
|
g.writeln('}')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_assert_metainfo(a ast.AssertStmt) string {
|
||||||
|
mod_path := cestring(g.file.path)
|
||||||
|
fn_name := g.fn_decl.name
|
||||||
|
line_nr := a.pos.line_nr
|
||||||
|
src := cestring(a.expr.str())
|
||||||
|
metaname := 'v_assert_meta_info_${g.new_tmp_var()}'
|
||||||
|
g.writeln(' VAssertMetaInfo $metaname;')
|
||||||
|
g.writeln(' memset(&$metaname, 0, sizeof(VAssertMetaInfo));')
|
||||||
|
g.writeln(' ${metaname}.fpath = ${ctoslit(mod_path)};')
|
||||||
|
g.writeln(' ${metaname}.line_nr = ${line_nr};')
|
||||||
|
g.writeln(' ${metaname}.fn_name = ${ctoslit(fn_name)};')
|
||||||
|
g.writeln(' ${metaname}.src = ${ctoslit(src)};')
|
||||||
|
match a.expr {
|
||||||
|
ast.InfixExpr {
|
||||||
|
g.writeln(' ${metaname}.op = ${ctoslit(it.op.str())};')
|
||||||
|
g.writeln(' ${metaname}.llabel = ${ctoslit(it.left.str())};')
|
||||||
|
g.writeln(' ${metaname}.rlabel = ${ctoslit(it.right.str())};')
|
||||||
|
g.write(' ${metaname}.lvalue = ')
|
||||||
|
g.gen_assert_single_expr(it.left, it.left_type)
|
||||||
|
g.writeln(';')
|
||||||
|
//
|
||||||
|
g.write(' ${metaname}.rvalue = ')
|
||||||
|
g.gen_assert_single_expr(it.right, it.right_type)
|
||||||
|
g.writeln(';')
|
||||||
|
}
|
||||||
|
ast.CallExpr {
|
||||||
|
g.writeln(' ${metaname}.op = tos_lit("call");')
|
||||||
|
}
|
||||||
|
else{}
|
||||||
|
}
|
||||||
|
return metaname
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_assert_single_expr(e ast.Expr, t table.Type) {
|
||||||
|
unknown_value := '*unknown value*'
|
||||||
|
match e {
|
||||||
|
ast.CallExpr { g.write( ctoslit(unknown_value) ) }
|
||||||
|
ast.CastExpr { g.write( ctoslit(unknown_value) ) }
|
||||||
|
ast.IndexExpr { g.write( ctoslit(unknown_value) ) }
|
||||||
|
ast.PrefixExpr { g.write( ctoslit(unknown_value) ) }
|
||||||
|
ast.MatchExpr { g.write( ctoslit(unknown_value) ) }
|
||||||
|
else{ g.gen_expr_to_string(e, t) }
|
||||||
|
}
|
||||||
|
g.write(' /* typeof: ' +typeof(e) + ' type: ' + t.str() + ' */ ')
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
||||||
if assign_stmt.is_static {
|
if assign_stmt.is_static {
|
||||||
g.write('static ')
|
g.write('static ')
|
||||||
|
@ -3018,67 +3070,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
}
|
}
|
||||||
} else if specs[i] == `s` {
|
} else if specs[i] == `s` {
|
||||||
sym := g.table.get_type_symbol(node.expr_types[i])
|
g.gen_expr_to_string(expr, node.expr_types[i])
|
||||||
sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
|
|
||||||
if node.expr_types[i].flag_is(.variadic) {
|
|
||||||
str_fn_name := g.gen_str_for_type(node.expr_types[i])
|
|
||||||
g.write('${str_fn_name}(')
|
|
||||||
g.expr(expr)
|
|
||||||
g.write(')')
|
|
||||||
} else if sym.kind == .enum_ {
|
|
||||||
is_var := match node.exprs[i] {
|
|
||||||
ast.SelectorExpr { true }
|
|
||||||
ast.Ident { true }
|
|
||||||
else { false }
|
|
||||||
}
|
|
||||||
if is_var {
|
|
||||||
str_fn_name := g.gen_str_for_type(node.expr_types[i])
|
|
||||||
g.write('${str_fn_name}(')
|
|
||||||
g.enum_expr(expr)
|
|
||||||
g.write(')')
|
|
||||||
} else {
|
|
||||||
g.write('tos_lit("')
|
|
||||||
g.enum_expr(expr)
|
|
||||||
g.write('")')
|
|
||||||
}
|
|
||||||
} else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_] {
|
|
||||||
is_p := node.expr_types[i].is_ptr()
|
|
||||||
val_type := if is_p { node.expr_types[i].deref() } else { node.expr_types[i] }
|
|
||||||
str_fn_name := g.gen_str_for_type(val_type)
|
|
||||||
if is_p && str_method_expects_ptr {
|
|
||||||
g.write('string_add(_SLIT("&"), ${str_fn_name}( (')
|
|
||||||
}
|
|
||||||
if is_p && !str_method_expects_ptr {
|
|
||||||
g.write('string_add(_SLIT("&"), ${str_fn_name}( *(')
|
|
||||||
}
|
|
||||||
if !is_p && !str_method_expects_ptr {
|
|
||||||
g.write('${str_fn_name}( ')
|
|
||||||
}
|
|
||||||
if !is_p && str_method_expects_ptr {
|
|
||||||
g.write('${str_fn_name}( &')
|
|
||||||
}
|
|
||||||
g.expr(expr)
|
|
||||||
if sym.kind == .struct_ && !sym_has_str_method {
|
|
||||||
if is_p {
|
|
||||||
g.write('),0))')
|
|
||||||
} else {
|
|
||||||
g.write(',0)')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if is_p {
|
|
||||||
g.write(')))')
|
|
||||||
} else {
|
|
||||||
g.write(')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if g.typ(node.expr_types[i]).starts_with('Option') {
|
|
||||||
str_fn_name := 'OptionBase_str'
|
|
||||||
g.write('${str_fn_name}(*(OptionBase*)&')
|
|
||||||
g.expr(expr)
|
|
||||||
g.write(')')
|
|
||||||
} else {
|
|
||||||
verror('cannot convert to string')
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
}
|
}
|
||||||
|
@ -3092,6 +3084,71 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
|
||||||
g.write(')')
|
g.write(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) ?bool {
|
||||||
|
sym := g.table.get_type_symbol(etype)
|
||||||
|
sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
|
||||||
|
if etype.flag_is(.variadic) {
|
||||||
|
str_fn_name := g.gen_str_for_type(etype)
|
||||||
|
g.write('${str_fn_name}(')
|
||||||
|
g.expr(expr)
|
||||||
|
g.write(')')
|
||||||
|
} else if sym.kind == .enum_ {
|
||||||
|
is_var := match expr {
|
||||||
|
ast.SelectorExpr { true }
|
||||||
|
ast.Ident { true }
|
||||||
|
else { false }
|
||||||
|
}
|
||||||
|
if is_var {
|
||||||
|
str_fn_name := g.gen_str_for_type(etype)
|
||||||
|
g.write('${str_fn_name}(')
|
||||||
|
g.enum_expr(expr)
|
||||||
|
g.write(')')
|
||||||
|
} else {
|
||||||
|
g.write('tos_lit("')
|
||||||
|
g.enum_expr(expr)
|
||||||
|
g.write('")')
|
||||||
|
}
|
||||||
|
} else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_] {
|
||||||
|
is_p := etype.is_ptr()
|
||||||
|
val_type := if is_p { etype.deref() } else { etype }
|
||||||
|
str_fn_name := g.gen_str_for_type(val_type)
|
||||||
|
if is_p && str_method_expects_ptr {
|
||||||
|
g.write('string_add(_SLIT("&"), ${str_fn_name}( (')
|
||||||
|
}
|
||||||
|
if is_p && !str_method_expects_ptr {
|
||||||
|
g.write('string_add(_SLIT("&"), ${str_fn_name}( *(')
|
||||||
|
}
|
||||||
|
if !is_p && !str_method_expects_ptr {
|
||||||
|
g.write('${str_fn_name}( ')
|
||||||
|
}
|
||||||
|
if !is_p && str_method_expects_ptr {
|
||||||
|
g.write('${str_fn_name}( &')
|
||||||
|
}
|
||||||
|
g.expr(expr)
|
||||||
|
if sym.kind == .struct_ && !sym_has_str_method {
|
||||||
|
if is_p {
|
||||||
|
g.write('),0))')
|
||||||
|
} else {
|
||||||
|
g.write(',0)')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if is_p {
|
||||||
|
g.write(')))')
|
||||||
|
} else {
|
||||||
|
g.write(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if g.typ(etype).starts_with('Option') {
|
||||||
|
str_fn_name := 'OptionBase_str'
|
||||||
|
g.write('${str_fn_name}(*(OptionBase*)&')
|
||||||
|
g.expr(expr)
|
||||||
|
g.write(')')
|
||||||
|
} else {
|
||||||
|
return error('cannot convert to string')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// `nums.map(it % 2 == 0)`
|
// `nums.map(it % 2 == 0)`
|
||||||
fn (mut g Gen) gen_map(node ast.CallExpr) {
|
fn (mut g Gen) gen_map(node ast.CallExpr) {
|
||||||
tmp := g.new_tmp_var()
|
tmp := g.new_tmp_var()
|
||||||
|
|
|
@ -9,7 +9,8 @@ struct IntegerLiteral {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(e Expr) string {
|
fn handle(e Expr) string {
|
||||||
assert e is IntegerLiteral
|
is_literal := e is IntegerLiteral
|
||||||
|
assert is_literal
|
||||||
if e is IntegerLiteral {
|
if e is IntegerLiteral {
|
||||||
println('int')
|
println('int')
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue