v: support dump(expr) (#9160)

pull/9168/head
Delyan Angelov 2021-03-06 19:09:28 +02:00 committed by GitHub
parent 747507dd1d
commit 849cde245c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 219 additions and 53 deletions

View File

@ -204,7 +204,7 @@ fn (mut f MDFile) parse_line(lnumber int, line string) {
}
}
fn (mut f MDFile) dump() {
fn (mut f MDFile) debug() {
for e in f.examples {
eprintln('f.path: $f.path | example: $e')
}

View File

@ -0,0 +1,10 @@
fn factorial(n u32) u32 {
if dump(n <= 1) {
return dump(1)
}
return dump(n * factorial(n - 1))
}
fn main() {
println(factorial(5))
}

View File

@ -11,10 +11,10 @@ pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl
pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall |
ComptimeSelector | ConcatExpr | EnumVal | FloatLiteral | GoExpr | Ident | IfExpr |
IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit |
MatchExpr | None | OffsetOf | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr |
SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
ComptimeSelector | ConcatExpr | DumpExpr | EnumVal | FloatLiteral | GoExpr | Ident |
IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr |
MapInit | MatchExpr | None | OffsetOf | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
StructInit | Type | TypeOf | UnsafeExpr
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
@ -1114,6 +1114,15 @@ pub mut:
expr_type table.Type
}
pub struct DumpExpr {
pub:
expr Expr
pos token.Position
pub mut:
expr_type table.Type
cname string // filled in the checker
}
pub struct Comment {
pub:
text string
@ -1240,7 +1249,7 @@ pub fn (expr Expr) position() token.Position {
return expr.decl.pos
}
ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit,
CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, EnumVal, FloatLiteral,
CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, EnumVal, DumpExpr, FloatLiteral,
GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr,
None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr,
SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr

View File

@ -1866,7 +1866,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
&& f.ctdefine !in c.pref.compile_defines {
call_expr.should_be_skipped = true
}
// dont check number of args for JS functions since arguments are not required
// dont check number of args for JS functions since arguments are not required
if call_expr.language != .js {
min_required_args := if f.is_variadic { f.params.len - 1 } else { f.params.len }
if call_expr.args.len < min_required_args {
@ -3716,6 +3716,17 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
ast.ConcatExpr {
return c.concat_expr(mut node)
}
ast.DumpExpr {
node.expr_type = c.expr(node.expr)
if node.expr_type.idx() == table.void_type_idx {
c.error('dump expression can not be void', node.expr.position())
return table.void_type
}
tsym := c.table.get_type_symbol(node.expr_type)
c.table.dumps[int(node.expr_type)] = tsym.cname
node.cname = tsym.cname
return node.expr_type
}
ast.EnumVal {
return c.enum_val(mut node)
}

View File

@ -0,0 +1,5 @@
vlib/v/checker/tests/dump_of_void_expr.vv:3:6: error: dump expression can not be void
1 | fn abc() {}
2 |
3 | dump(abc())
| ~~~~~

View File

@ -0,0 +1,3 @@
fn abc() {}
dump(abc())

View File

@ -900,6 +900,11 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
ast.CTempVar {
eprintln('ast.CTempVar of $node.orig.str() should be generated/used only in cgen')
}
ast.DumpExpr {
f.write('dump(')
f.expr(node.expr)
f.write(')')
}
ast.AnonFn {
f.fn_decl(node.decl)
}

View File

@ -266,6 +266,8 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
g.definitions.writeln('int _v_type_idx_${typ.cname}() { return $idx; };')
}
}
//
g.dump_expr_definitions()
// v files are finished, what remains is pure C code
g.gen_vlines_reset()
if g.pref.build_mode != .build_module {
@ -2698,6 +2700,9 @@ fn (mut g Gen) expr(node ast.Expr) {
g.write("L'$node.val'")
}
}
ast.DumpExpr {
g.dump_expr(node)
}
ast.AtExpr {
g.comp_at(node)
}
@ -2965,7 +2970,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
g.write('(*(')
}
if sym.kind == .array_fixed {
assert node.field_name == 'len'
if node.field_name != 'len' {
g.error('field_name should be `len`', node.pos)
}
info := sym.info as table.ArrayFixed
g.write('$info.size')
return

View File

@ -0,0 +1,39 @@
module c
import v.ast
fn (mut g Gen) dump_expr(node ast.DumpExpr) {
sexpr := ctoslit(node.expr.str())
fpath := cestring(g.file.path)
line := node.pos.line_nr + 1
if 'nop_dump' in g.pref.compile_defines {
g.expr(node.expr)
return
}
g.write(' _v_dump_expr_${node.cname}(${ctoslit(fpath)}, $line, $sexpr, ')
g.expr(node.expr)
g.write(' )')
}
fn (mut g Gen) dump_expr_definitions() {
if g.pref.build_mode == .build_module {
for _, cname in g.table.dumps {
g.definitions.writeln('$cname _v_dump_expr_${cname}(string fpath, int line, string sexpr, $cname x);')
}
} else {
for dump_type, cname in g.table.dumps {
to_string_fn_name := g.gen_str_for_type(dump_type)
g.definitions.writeln('$cname _v_dump_expr_${cname}(string fpath, int line, string sexpr, $cname x) {')
g.definitions.writeln('\teprint(${ctoslit('[')});')
g.definitions.writeln('\teprint(fpath);')
g.definitions.writeln('\teprint(${ctoslit(':')});')
g.definitions.writeln('\teprint(int_str(line));')
g.definitions.writeln('\teprint(${ctoslit('] ')});')
g.definitions.writeln('\teprint(sexpr);')
g.definitions.writeln('\teprint(${ctoslit(': ')});')
g.definitions.writeln('\teprintln(${to_string_fn_name}(x));')
g.definitions.writeln('\treturn x;')
g.definitions.writeln('}')
}
}
}

View File

@ -10,19 +10,19 @@ import v.depgraph
const (
// https://ecma-international.org/ecma-262/#sec-reserved-words
js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function',
'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package',
'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw',
'try', 'typeof', 'var', 'void', 'while', 'with', 'yield', 'Number', 'String', 'Boolean',
'Array', 'Map']
// used to generate type structs
v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', 'int_literal',
'float_literal', 'size_t', 'bool', 'string', 'map', 'array']
shallow_equatables = [table.Kind.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .int_literal,
.float_literal, .size_t, .bool, .string]
tabs = ['', '\t', '\t\t', '\t\t\t', '\t\t\t\t', '\t\t\t\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t',
'\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t']
v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64',
'int_literal', 'float_literal', 'size_t', 'bool', 'string', 'map', 'array']
shallow_equatables = [table.Kind.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64,
.int_literal, .float_literal, .size_t, .bool, .string]
tabs = ['', '\t', '\t\t', '\t\t\t', '\t\t\t\t', '\t\t\t\t\t', '\t\t\t\t\t\t',
'\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t\t']
)
struct Namespace {
@ -60,8 +60,8 @@ mut:
method_fn_decls map[string][]ast.FnDecl
builtin_fns []string // Functions defined in `builtin`
empty_line bool
cast_stack []table.Type
call_stack []ast.CallExpr
cast_stack []table.Type
call_stack []ast.CallExpr
}
pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string {
@ -115,7 +115,7 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
mut out := g.hashes() + g.definitions.str()
// equality check for js objects
// TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js')
//unsafe {
// unsafe {
// mut eq_fn := $embed_file('fast_deep_equal.js')
// out += eq_fn.data().vstring()
//}
@ -447,6 +447,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
ast.CTempVar {
g.write('/* ast.CTempVar: node.name */')
}
ast.DumpExpr {
g.write('/* ast.DumpExpr: $node.expr */')
}
ast.AnonFn {
g.gen_fn_decl(node.decl)
}
@ -689,10 +692,12 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
} else {
g.write(' $op ')
// TODO: Multiple types??
should_cast := g.table.type_kind(stmt.left_types.first()) in shallow_equatables
should_cast := g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables
if should_cast {
g.cast_stack << stmt.left_types.first()
if g.file.mod.name == 'builtin' { g.write('new ') }
if g.file.mod.name == 'builtin' {
g.write('new ')
}
g.write('${g.typ(stmt.left_types.first())}(')
}
g.expr(val)
@ -845,7 +850,9 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
}
g.fn_args(args, it.is_variadic)
if it.is_method {
if args.len > 0 { g.write(', ') }
if args.len > 0 {
g.write(', ')
}
g.write('${it.params[0].name} = this')
}
g.writeln(') {')
@ -926,13 +933,23 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) {
if val !in ['', '_'] {
g.write('\tconst $val = ')
if it.kind == .string {
if g.file.mod.name == 'builtin' { g.write('new ') }
if g.file.mod.name == 'builtin' {
g.write('new ')
}
g.write('byte(')
}
g.expr(it.cond)
g.write(if it.kind == .array { '.arr' } else if it.kind == .string { '.str' } else { '.val' })
g.write(if it.kind == .array {
'.arr'
} else if it.kind == .string {
'.str'
} else {
'.val'
})
g.write('[$i]')
if it.kind == .string { g.write(')') }
if it.kind == .string {
g.write(')')
}
g.writeln(';')
}
g.stmts(it.stmts)
@ -1369,11 +1386,13 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
r_sym := g.table.get_type_symbol(it.right_type)
is_not := it.op in [.not_in, .not_is, .ne]
if is_not { g.write('!(') }
if is_not {
g.write('!(')
}
if it.op == .eq || it.op == .ne {
// Shallow equatables
if l_sym.kind in shallow_equatables && r_sym.kind in shallow_equatables {
if l_sym.kind in js.shallow_equatables && r_sym.kind in js.shallow_equatables {
g.expr(it.left)
g.write('.eq(')
g.expr(it.right)
@ -1404,7 +1423,9 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
'.includes('
})
g.expr(it.left)
if l_sym.kind == .string { g.write('.str') }
if l_sym.kind == .string {
g.write('.str')
}
g.write(')')
} else if it.op in [.key_is, .not_is] { // foo is Foo
g.expr(it.left)
@ -1423,7 +1444,7 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
g.cast_stack << greater_typ
}
g.expr(it.left)
g.write(' ${it.op} ')
g.write(' $it.op ')
g.expr(it.right)
if is_arithmetic && needs_cast {
@ -1432,7 +1453,9 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
}
}
if is_not { g.write(')') }
if is_not {
g.write(')')
}
}
fn (mut g JsGen) greater_typ(left table.Type, right table.Type) table.Type {
@ -1560,7 +1583,7 @@ fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) {
if g.file.mod.name == 'builtin' {
g.write('new ')
}
g.write("string(")
g.write('string(')
}
g.write("'$text'")
if should_cast {
@ -1651,15 +1674,15 @@ fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) {
// TODO: call.language always seems to be "v", parser bug?
if g.call_stack.len > 0 {
call := g.call_stack[g.call_stack.len - 1]
//if call.language == .js {
for t in call.args {
if t.expr is ast.IntegerLiteral {
if t.expr == it {
g.write(it.val)
return
}
// if call.language == .js {
for t in call.args {
if t.expr is ast.IntegerLiteral {
if t.expr == it {
g.write(it.val)
return
}
}
}
//}
}
@ -1680,24 +1703,24 @@ fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) {
fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) {
typ := table.Type(table.f32_type)
// Don't wrap integers for use in JS.foo functions.
// TODO: call.language always seems to be "v", parser bug?
if g.call_stack.len > 0 {
call := g.call_stack[g.call_stack.len - 1]
//if call.language == .js {
for i, t in call.args {
if t.expr is ast.FloatLiteral {
if t.expr == it {
if call.expected_arg_types[i] in table.integer_type_idxs {
g.write(int(it.val.f64()).str())
} else {
g.write(it.val)
}
return
// if call.language == .js {
for i, t in call.args {
if t.expr is ast.FloatLiteral {
if t.expr == it {
if call.expected_arg_types[i] in table.integer_type_idxs {
g.write(int(it.val.f64()).str())
} else {
g.write(it.val)
}
return
}
}
}
//}
}

View File

@ -180,6 +180,9 @@ fn (mut w Walker) expr(node ast.Expr) {
w.stmts(node.vweb_tmpl.stmts)
}
}
ast.DumpExpr {
w.expr(node.expr)
}
ast.GoExpr {
w.expr(node.go_stmt.call_expr)
}

View File

@ -670,6 +670,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
p.label_names << name
p.next()
if p.tok.kind == .key_for {
for_pos := p.tok.position()
mut stmt := p.stmt(is_top_level)
match mut stmt {
ast.ForStmt {
@ -685,7 +686,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
return stmt
}
else {
assert false
p.error_with_pos('unknown kind of For statement', for_pos)
}
}
}
@ -1031,7 +1032,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt {
if node !is ast.CallExpr && (is_top_level || p.tok.kind != .rcbr)
&& node !is ast.PostfixExpr && !(node is ast.InfixExpr
&& (node as ast.InfixExpr).op in [.left_shift, .arrow]) && node !is ast.ComptimeCall
&& node !is ast.SelectorExpr {
&& node !is ast.SelectorExpr && node !is ast.DumpExpr {
p.error_with_pos('expression evaluated but not used', node.position())
return ast.Stmt{}
}

View File

@ -214,6 +214,17 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr {
pos: spos.extend(p.tok.position())
}
}
.key_dump {
spos := p.tok.position()
p.next()
p.check(.lpar)
expr := p.expr(0)
p.check(.rpar)
node = ast.DumpExpr{
expr: expr
pos: spos.extend(p.tok.position())
}
}
.key_offsetof {
pos := p.tok.position()
p.next() // __offsetof

View File

@ -12,8 +12,9 @@ pub mut:
types []TypeSymbol
type_idxs map[string]int
fns map[string]Fn
imports []string // List of all imports
modules []string // Topologically sorted list of all modules registered by the application
dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions
imports []string // List of all imports
modules []string // Topologically sorted list of all modules registered by the application
cflags []cflag.CFlag
redefined_fns []string
fn_gen_types map[string][][]Type // for generic functions

View File

@ -0,0 +1,7 @@
[vlib/v/tests/inout/dump_expression.vv:2] 1: 1
[vlib/v/tests/inout/dump_expression.vv:7] 'a': a
[vlib/v/tests/inout/dump_expression.vv:20] point: Point{
x: 1
y: 2
z: 3
}

View File

@ -0,0 +1,29 @@
fn dump_of_int() {
x := dump(1) + 1
assert x == 2
}
fn dump_of_string() {
x := dump('a') + 'b'
assert x == 'ab'
}
struct Point {
mut:
x int
y int
z int
}
fn dump_of_struct() {
point := Point{1, 2, 3}
mut x := dump(point)
x.x += 100
assert x == Point{101, 2, 3}
}
fn main() {
dump_of_int()
dump_of_string()
dump_of_struct()
}

View File

@ -117,6 +117,7 @@ pub enum Kind {
key_true
key_type
key_typeof
key_dump
key_orelse
key_union
key_pub
@ -274,6 +275,7 @@ fn build_token_str() []string {
s[Kind.key_import] = 'import'
s[Kind.key_unsafe] = 'unsafe'
s[Kind.key_typeof] = 'typeof'
s[Kind.key_dump] = 'dump'
s[Kind.key_enum] = 'enum'
s[Kind.key_interface] = 'interface'
s[Kind.key_pub] = 'pub'

View File

@ -42,7 +42,7 @@ pub fn new_mod_file_cacher() &ModFileCacher {
return &ModFileCacher{}
}
pub fn (mcache &ModFileCacher) dump() {
pub fn (mcache &ModFileCacher) debug() {
$if debug {
eprintln('ModFileCacher DUMP:')
eprintln(' ModFileCacher.cache:')