all: initial support for closures (x64 / linux-only) (#11114)
parent
2cfb8fd697
commit
da53f818df
|
@ -97,6 +97,7 @@ const (
|
|||
skip_on_windows = [
|
||||
'vlib/orm/orm_test.v',
|
||||
'vlib/v/tests/orm_sub_struct_test.v',
|
||||
'vlib/v/tests/closure_test.v',
|
||||
'vlib/net/websocket/ws_test.v',
|
||||
'vlib/net/unix/unix_test.v',
|
||||
'vlib/net/websocket/websocket_test.v',
|
||||
|
|
|
@ -22,8 +22,6 @@ pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor
|
|||
GlobalDecl | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | NodeError |
|
||||
Return | SqlStmt | StructDecl | TypeDecl
|
||||
|
||||
// NB: when you add a new Expr or Stmt type with a .pos field, remember to update
|
||||
// the .position() token.Position methods too.
|
||||
pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var
|
||||
|
||||
// TODO: replace Param
|
||||
|
@ -353,9 +351,10 @@ pub:
|
|||
// anonymous function
|
||||
pub struct AnonFn {
|
||||
pub mut:
|
||||
decl FnDecl
|
||||
typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
|
||||
has_gen bool // has been generated
|
||||
decl FnDecl
|
||||
inherited_vars []Param
|
||||
typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
|
||||
has_gen bool // has been generated
|
||||
}
|
||||
|
||||
// function or method declaration
|
||||
|
@ -498,6 +497,7 @@ pub:
|
|||
is_autofree_tmp bool
|
||||
is_arg bool // fn args should not be autofreed
|
||||
is_auto_deref bool
|
||||
is_inherited bool
|
||||
pub mut:
|
||||
typ Type
|
||||
orig_type Type // original sumtype type; 0 if it's not a sumtype
|
||||
|
@ -1539,7 +1539,7 @@ pub fn (expr Expr) position() token.Position {
|
|||
AnonFn {
|
||||
return expr.decl.pos
|
||||
}
|
||||
EmptyExpr {
|
||||
CTempVar, EmptyExpr {
|
||||
// println('compiler bug, unhandled EmptyExpr position()')
|
||||
return token.Position{}
|
||||
}
|
||||
|
@ -1571,9 +1571,6 @@ pub fn (expr Expr) position() token.Position {
|
|||
last_line: right_pos.last_line
|
||||
}
|
||||
}
|
||||
CTempVar {
|
||||
return token.Position{}
|
||||
}
|
||||
// Please, do NOT use else{} here.
|
||||
// This match is exhaustive *on purpose*, to help force
|
||||
// maintaining/implementing proper .pos fields.
|
||||
|
|
|
@ -41,20 +41,6 @@ fn (s &Scope) dont_lookup_parent() bool {
|
|||
return isnil(s.parent) || s.detached_from_parent
|
||||
}
|
||||
|
||||
pub fn (s &Scope) find_with_scope(name string) ?(ScopeObject, &Scope) {
|
||||
mut sc := s
|
||||
for {
|
||||
if name in sc.objects {
|
||||
return sc.objects[name], sc
|
||||
}
|
||||
if sc.dont_lookup_parent() {
|
||||
break
|
||||
}
|
||||
sc = sc.parent
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (s &Scope) find(name string) ?ScopeObject {
|
||||
for sc := s; true; sc = sc.parent {
|
||||
if name in sc.objects {
|
||||
|
@ -82,14 +68,6 @@ pub fn (s &Scope) find_struct_field(name string, struct_type Type, field_name st
|
|||
return none
|
||||
}
|
||||
|
||||
pub fn (s &Scope) is_known(name string) bool {
|
||||
if _ := s.find(name) {
|
||||
return true
|
||||
} else {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (s &Scope) find_var(name string) ?&Var {
|
||||
if obj := s.find(name) {
|
||||
match obj {
|
||||
|
@ -121,23 +99,16 @@ pub fn (s &Scope) find_const(name string) ?&ConstField {
|
|||
}
|
||||
|
||||
pub fn (s &Scope) known_var(name string) bool {
|
||||
if _ := s.find_var(name) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
s.find_var(name) or { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
pub fn (mut s Scope) update_var_type(name string, typ Type) {
|
||||
s.end_pos = s.end_pos // TODO mut bug
|
||||
mut obj := s.objects[name]
|
||||
match mut obj {
|
||||
Var {
|
||||
if obj.typ == typ {
|
||||
return
|
||||
}
|
||||
if mut obj is Var {
|
||||
if obj.typ != typ {
|
||||
obj.typ = typ
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,29 +123,10 @@ pub fn (mut s Scope) register_struct_field(name string, field ScopeStructField)
|
|||
}
|
||||
|
||||
pub fn (mut s Scope) register(obj ScopeObject) {
|
||||
name := if obj is ConstField {
|
||||
obj.name
|
||||
} else if obj is GlobalField {
|
||||
obj.name
|
||||
} else {
|
||||
(obj as Var).name
|
||||
}
|
||||
if name == '_' {
|
||||
if obj.name == '_' || obj.name in s.objects {
|
||||
return
|
||||
}
|
||||
if name in s.objects {
|
||||
// println('existing obect: $name')
|
||||
return
|
||||
}
|
||||
s.objects[name] = obj
|
||||
}
|
||||
|
||||
pub fn (s &Scope) outermost() &Scope {
|
||||
mut sc := s
|
||||
for !sc.dont_lookup_parent() {
|
||||
sc = sc.parent
|
||||
}
|
||||
return sc
|
||||
s.objects[obj.name] = obj
|
||||
}
|
||||
|
||||
// returns the innermost scope containing pos
|
||||
|
@ -211,6 +163,17 @@ pub fn (s &Scope) contains(pos int) bool {
|
|||
return pos >= s.start_pos && pos <= s.end_pos
|
||||
}
|
||||
|
||||
pub fn (s &Scope) has_inherited_vars() bool {
|
||||
for _, obj in s.objects {
|
||||
if obj is Var {
|
||||
if obj.is_inherited {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (sc Scope) show(depth int, max_depth int) string {
|
||||
mut out := ''
|
||||
mut indent := ''
|
||||
|
|
|
@ -34,50 +34,61 @@ pub fn (node &CallExpr) fkey() string {
|
|||
}
|
||||
|
||||
// These methods are used only by vfmt, vdoc, and for debugging.
|
||||
pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) string {
|
||||
mut f := strings.new_builder(30)
|
||||
f.write_string('fn ')
|
||||
if node.inherited_vars.len > 0 {
|
||||
f.write_string('[')
|
||||
for i, var in node.inherited_vars {
|
||||
if i > 0 {
|
||||
f.write_string(', ')
|
||||
}
|
||||
if var.is_mut {
|
||||
f.write_string('mut ')
|
||||
}
|
||||
f.write_string(var.name)
|
||||
}
|
||||
f.write_string('] ')
|
||||
}
|
||||
stringify_fn_after_name(node.decl, mut f, t, cur_mod, m2a)
|
||||
return f.str()
|
||||
}
|
||||
|
||||
pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string {
|
||||
mut f := strings.new_builder(30)
|
||||
if node.is_pub {
|
||||
f.write_string('pub ')
|
||||
}
|
||||
mut receiver := ''
|
||||
f.write_string('fn ')
|
||||
if node.is_method {
|
||||
f.write_string('(')
|
||||
mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
|
||||
cur_mod)
|
||||
m := if node.rec_mut { node.receiver.typ.share().str() + ' ' } else { '' }
|
||||
if node.rec_mut {
|
||||
f.write_string(node.receiver.typ.share().str() + ' ')
|
||||
styp = styp[1..] // remove &
|
||||
}
|
||||
f.write_string(node.receiver.name + ' ')
|
||||
styp = util.no_cur_mod(styp, cur_mod)
|
||||
if node.params[0].is_auto_rec {
|
||||
styp = styp.trim('&')
|
||||
}
|
||||
receiver = '($m$node.receiver.name $styp) '
|
||||
/*
|
||||
sym := t.get_type_symbol(node.receiver.typ)
|
||||
name := sym.name.after('.')
|
||||
mut m := if node.rec_mut { 'mut ' } else { '' }
|
||||
if !node.rec_mut && node.receiver.typ.is_ptr() {
|
||||
m = '&'
|
||||
}
|
||||
receiver = '($node.receiver.name $m$name) '
|
||||
*/
|
||||
f.write_string(styp + ') ')
|
||||
}
|
||||
mut name := if node.is_anon { '' } else { node.name }
|
||||
if !node.is_anon && !node.is_method && node.language == .v {
|
||||
name = node.name.all_after_last('.')
|
||||
name := if !node.is_method && node.language == .v {
|
||||
node.name.all_after_last('.')
|
||||
} else {
|
||||
node.name
|
||||
}
|
||||
// mut name := if node.is_anon { '' } else { node.name.after_char(`.`) }
|
||||
// if !node.is_method {
|
||||
// if node.language == .c {
|
||||
// name = 'C.$name'
|
||||
// } else if node.language == .js {
|
||||
// name = 'JS.$name'
|
||||
// }
|
||||
// }
|
||||
f.write_string('fn $receiver$name')
|
||||
f.write_string(name)
|
||||
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] {
|
||||
f.write_string(' ')
|
||||
}
|
||||
stringify_fn_after_name(node, mut f, t, cur_mod, m2a)
|
||||
return f.str()
|
||||
}
|
||||
|
||||
fn stringify_fn_after_name(node &FnDecl, mut f strings.Builder, t &Table, cur_mod string, m2a map[string]string) {
|
||||
mut add_para_types := true
|
||||
if node.generic_names.len > 0 {
|
||||
if node.is_method {
|
||||
|
@ -151,7 +162,6 @@ pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string)
|
|||
}
|
||||
f.write_string(' ' + rs)
|
||||
}
|
||||
return f.str()
|
||||
}
|
||||
|
||||
// Expressions in string interpolations may have to be put in braces if they
|
||||
|
|
|
@ -2737,7 +2737,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
|
|||
}
|
||||
call_expr.is_noreturn = func.is_noreturn
|
||||
if !found_in_args {
|
||||
if _ := call_expr.scope.find_var(fn_name) {
|
||||
if call_expr.scope.known_var(fn_name) {
|
||||
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
|
||||
call_expr.pos)
|
||||
}
|
||||
|
@ -5210,14 +5210,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
|
|||
return node.typ
|
||||
}
|
||||
ast.AnonFn {
|
||||
c.inside_anon_fn = true
|
||||
keep_fn := c.table.cur_fn
|
||||
c.table.cur_fn = unsafe { &node.decl }
|
||||
c.stmts(node.decl.stmts)
|
||||
c.fn_decl(mut node.decl)
|
||||
c.table.cur_fn = keep_fn
|
||||
c.inside_anon_fn = false
|
||||
return node.typ
|
||||
return c.anon_fn(mut node)
|
||||
}
|
||||
ast.ArrayDecompose {
|
||||
typ := c.expr(node.expr)
|
||||
|
@ -8167,6 +8160,30 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
|||
node.source_file = c.file
|
||||
}
|
||||
|
||||
fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type {
|
||||
keep_fn := c.table.cur_fn
|
||||
keep_inside_anon := c.inside_anon_fn
|
||||
defer {
|
||||
c.table.cur_fn = keep_fn
|
||||
c.inside_anon_fn = keep_inside_anon
|
||||
}
|
||||
c.table.cur_fn = unsafe { &node.decl }
|
||||
c.inside_anon_fn = true
|
||||
for mut var in node.inherited_vars {
|
||||
parent_var := node.decl.scope.parent.find_var(var.name) or {
|
||||
panic('unexpected checker error: cannot find parent of inherited variable `$var.name`')
|
||||
}
|
||||
if var.is_mut && !parent_var.is_mut {
|
||||
c.error('original `$parent_var.name` is immutable, declare it with `mut` to make it mutable',
|
||||
var.pos)
|
||||
}
|
||||
var.typ = parent_var.typ
|
||||
}
|
||||
c.stmts(node.decl.stmts)
|
||||
c.fn_decl(mut node.decl)
|
||||
return node.typ
|
||||
}
|
||||
|
||||
// NB: has_top_return/1 should be called on *already checked* stmts,
|
||||
// which do have their stmt.expr.is_noreturn set properly:
|
||||
fn has_top_return(stmts []ast.Stmt) bool {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
vlib/v/checker/tests/closure_immutable.vv:4:3: error: `a` is immutable, declare it with `mut` to make it mutable
|
||||
2 | a := 1
|
||||
3 | f1 := fn [a] () {
|
||||
4 | a++
|
||||
| ^
|
||||
5 | println(a)
|
||||
6 | }
|
||||
vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable, declare it with `mut` to make it mutable
|
||||
5 | println(a)
|
||||
6 | }
|
||||
7 | f2 := fn [mut a] () {
|
||||
| ^
|
||||
8 | a++
|
||||
9 | println(a)
|
||||
vlib/v/checker/tests/closure_immutable.vv:13:3: error: `b` is immutable, declare it with `mut` to make it mutable
|
||||
11 | mut b := 2
|
||||
12 | f3 := fn [b] () {
|
||||
13 | b++
|
||||
| ^
|
||||
14 | println(b)
|
||||
15 | }
|
|
@ -0,0 +1,19 @@
|
|||
fn my_fn() {
|
||||
a := 1
|
||||
f1 := fn [a] () {
|
||||
a++
|
||||
println(a)
|
||||
}
|
||||
f2 := fn [mut a] () {
|
||||
a++
|
||||
println(a)
|
||||
}
|
||||
mut b := 2
|
||||
f3 := fn [b] () {
|
||||
b++
|
||||
println(b)
|
||||
}
|
||||
f1()
|
||||
f2()
|
||||
f3()
|
||||
}
|
|
@ -391,8 +391,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) {
|
|||
eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}')
|
||||
}
|
||||
match node {
|
||||
ast.NodeError {}
|
||||
ast.EmptyStmt {}
|
||||
ast.EmptyStmt, ast.NodeError {}
|
||||
ast.AsmStmt {
|
||||
f.asm_stmt(node)
|
||||
}
|
||||
|
@ -491,7 +490,7 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
|
|||
ast.NodeError {}
|
||||
ast.EmptyExpr {}
|
||||
ast.AnonFn {
|
||||
f.fn_decl(node.decl)
|
||||
f.anon_fn(node)
|
||||
}
|
||||
ast.ArrayDecompose {
|
||||
f.array_decompose(node)
|
||||
|
@ -868,6 +867,15 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) {
|
|||
pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
|
||||
f.attrs(node.attrs)
|
||||
f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
|
||||
f.fn_body(node)
|
||||
}
|
||||
|
||||
pub fn (mut f Fmt) anon_fn(node ast.AnonFn) {
|
||||
f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
|
||||
f.fn_body(node.decl)
|
||||
}
|
||||
|
||||
fn (mut f Fmt) fn_body(node ast.FnDecl) {
|
||||
if node.language == .v {
|
||||
if !node.no_body {
|
||||
f.write(' {')
|
||||
|
@ -1007,7 +1015,7 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) {
|
|||
|
||||
pub fn (mut f Fmt) go_expr(node ast.GoExpr) {
|
||||
f.write('go ')
|
||||
f.expr(node.call_expr)
|
||||
f.call_expr(node.call_expr)
|
||||
}
|
||||
|
||||
pub fn (mut f Fmt) goto_label(node ast.GotoLabel) {
|
||||
|
@ -1500,7 +1508,7 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) {
|
|||
} else {
|
||||
f.write_language_prefix(node.language)
|
||||
if node.left is ast.AnonFn {
|
||||
f.fn_decl(node.left.decl)
|
||||
f.anon_fn(node.left)
|
||||
} else if node.language != .v {
|
||||
f.write('${node.name.after_char(`.`)}')
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
my_var := 1
|
||||
my_simple_closure := fn [my_var] () {
|
||||
println(my_var)
|
||||
}
|
||||
my_closure_returns := fn [my_var] () int {
|
||||
return my_var
|
||||
}
|
||||
my_closure_has_params := fn [my_var, my_closure_returns] (add int) {
|
||||
println(my_var + my_closure_returns() + add)
|
||||
}
|
|
@ -180,6 +180,7 @@ mut:
|
|||
defer_vars []string
|
||||
anon_fn bool
|
||||
array_sort_fn map[string]bool
|
||||
nr_closures int
|
||||
}
|
||||
|
||||
pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
||||
|
@ -381,6 +382,10 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
|||
}
|
||||
}
|
||||
if g.anon_fn_definitions.len > 0 {
|
||||
if g.nr_closures > 0 {
|
||||
b.writeln('\n// V closure helpers')
|
||||
b.writeln(c_closure_helpers())
|
||||
}
|
||||
for fn_def in g.anon_fn_definitions {
|
||||
b.writeln(fn_def)
|
||||
}
|
||||
|
@ -613,9 +618,7 @@ fn (mut g Gen) generic_fn_name(types []ast.Type, before string, is_decl bool) st
|
|||
fn (mut g Gen) expr_string(expr ast.Expr) string {
|
||||
pos := g.out.len
|
||||
g.expr(expr)
|
||||
expr_str := g.out.after(pos)
|
||||
g.out.go_back(expr_str.len)
|
||||
return expr_str.trim_space()
|
||||
return g.out.cut_to(pos).trim_space()
|
||||
}
|
||||
|
||||
// Surround a potentially multi-statement expression safely with `prepend` and `append`.
|
||||
|
@ -623,13 +626,13 @@ fn (mut g Gen) expr_string(expr ast.Expr) string {
|
|||
fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string {
|
||||
pos := g.out.len
|
||||
g.stmt_path_pos << pos
|
||||
defer {
|
||||
g.stmt_path_pos.delete_last()
|
||||
}
|
||||
g.write(prepend)
|
||||
g.expr(expr)
|
||||
g.write(append)
|
||||
expr_str := g.out.after(pos)
|
||||
g.out.go_back(expr_str.len)
|
||||
g.stmt_path_pos.delete_last()
|
||||
return expr_str
|
||||
return g.out.cut_to(pos)
|
||||
}
|
||||
|
||||
// TODO this really shouldnt be seperate from typ
|
||||
|
@ -823,8 +826,7 @@ pub fn (mut g Gen) write_typedef_types() {
|
|||
if elem_sym.info is ast.FnType {
|
||||
pos := g.out.len
|
||||
g.write_fn_ptr_decl(&elem_sym.info, '')
|
||||
fixed = g.out.after(pos)
|
||||
g.out.go_back(fixed.len)
|
||||
fixed = g.out.cut_to(pos)
|
||||
mut def_str := 'typedef $fixed;'
|
||||
def_str = def_str.replace_once('(*)', '(*$styp[$len])')
|
||||
g.type_definitions.writeln(def_str)
|
||||
|
@ -2623,7 +2625,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|||
ret_styp := g.typ(val.decl.return_type)
|
||||
g.write('$ret_styp (*$ident.name) (')
|
||||
def_pos := g.definitions.len
|
||||
g.fn_args(val.decl.params, val.decl.is_variadic, voidptr(0))
|
||||
g.fn_args(val.decl.params, voidptr(0))
|
||||
g.definitions.go_back(g.definitions.len - def_pos)
|
||||
g.write(') = ')
|
||||
} else {
|
||||
|
@ -2764,7 +2766,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|||
ret_styp := g.typ(func.func.return_type)
|
||||
g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (')
|
||||
def_pos := g.definitions.len
|
||||
g.fn_args(func.func.params, func.func.is_variadic, voidptr(0))
|
||||
g.fn_args(func.func.params, voidptr(0))
|
||||
g.definitions.go_back(g.definitions.len - def_pos)
|
||||
g.write(')')
|
||||
} else {
|
||||
|
@ -3084,6 +3086,10 @@ fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int
|
|||
g.trace_autofree('// skipping tmp var "$obj.name"')
|
||||
continue
|
||||
}
|
||||
if obj.is_inherited {
|
||||
g.trace_autofree('// skipping inherited var "$obj.name"')
|
||||
continue
|
||||
}
|
||||
// if var.typ == 0 {
|
||||
// // TODO why 0?
|
||||
// continue
|
||||
|
@ -3194,19 +3200,6 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
|
||||
if !node.has_gen {
|
||||
pos := g.out.len
|
||||
g.anon_fn = true
|
||||
g.stmt(node.decl)
|
||||
g.anon_fn = false
|
||||
fn_body := g.out.after(pos)
|
||||
g.out.go_back(fn_body.len)
|
||||
g.anon_fn_definitions << fn_body
|
||||
node.has_gen = true
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) {
|
||||
mut hash_fn := ''
|
||||
mut key_eq_fn := ''
|
||||
|
@ -3270,10 +3263,7 @@ fn (mut g Gen) expr(node ast.Expr) {
|
|||
g.error('g.expr(): unhandled EmptyExpr', token.Position{})
|
||||
}
|
||||
ast.AnonFn {
|
||||
// TODO: dont fiddle with buffers
|
||||
g.gen_anon_fn_decl(mut node)
|
||||
fsym := g.table.get_type_symbol(node.typ)
|
||||
g.write(fsym.name)
|
||||
g.gen_anon_fn(mut node)
|
||||
}
|
||||
ast.ArrayDecompose {
|
||||
g.expr(node.expr)
|
||||
|
@ -4439,6 +4429,9 @@ fn (mut g Gen) ident(node ast.Ident) {
|
|||
return
|
||||
}
|
||||
}
|
||||
if v.is_inherited {
|
||||
g.write(closure_ctx + '->')
|
||||
}
|
||||
}
|
||||
} else if node_info is ast.IdentFn {
|
||||
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
|
||||
|
@ -5703,8 +5696,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
|
|||
// optional and we dont want that
|
||||
styp, base := g.optional_type_name(field.typ)
|
||||
if styp !in g.optionals {
|
||||
last_text := g.type_definitions.after(start_pos).clone()
|
||||
g.type_definitions.go_back_to(start_pos)
|
||||
last_text := g.type_definitions.cut_to(start_pos).clone()
|
||||
g.optionals << styp
|
||||
g.typedefs2.writeln('typedef struct $styp $styp;')
|
||||
g.type_definitions.writeln('${g.optional_type_text(styp,
|
||||
|
@ -5796,8 +5788,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
|
|||
if elem_sym.info is ast.FnType {
|
||||
pos := g.out.len
|
||||
g.write_fn_ptr_decl(&elem_sym.info, '')
|
||||
fixed_elem_name = g.out.after(pos)
|
||||
g.out.go_back(fixed_elem_name.len)
|
||||
fixed_elem_name = g.out.cut_to(pos)
|
||||
mut def_str := 'typedef $fixed_elem_name;'
|
||||
def_str = def_str.replace_once('(*)', '(*$styp[$len])')
|
||||
g.type_definitions.writeln(def_str)
|
||||
|
@ -5883,9 +5874,7 @@ fn (g &Gen) nth_stmt_pos(n int) int {
|
|||
|
||||
fn (mut g Gen) go_before_stmt(n int) string {
|
||||
stmt_pos := g.nth_stmt_pos(n)
|
||||
cur_line := g.out.after(stmt_pos)
|
||||
g.out.go_back(cur_line.len)
|
||||
return cur_line
|
||||
return g.out.cut_to(stmt_pos)
|
||||
}
|
||||
|
||||
[inline]
|
||||
|
@ -6167,8 +6156,7 @@ fn (mut g Gen) go_expr(node ast.GoExpr) {
|
|||
name = receiver_sym.name + '_' + name
|
||||
} else if mut expr.left is ast.AnonFn {
|
||||
g.gen_anon_fn_decl(mut expr.left)
|
||||
fsym := g.table.get_type_symbol(expr.left.typ)
|
||||
name = fsym.name
|
||||
name = expr.left.decl.name
|
||||
}
|
||||
name = util.no_dots(name)
|
||||
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
|
||||
|
@ -6465,7 +6453,7 @@ fn (mut g Gen) interface_table() string {
|
|||
arg := method.params[i]
|
||||
methods_struct_def.write_string(', ${g.typ(arg.typ)} $arg.name')
|
||||
}
|
||||
// TODO g.fn_args(method.args[1..], method.is_variadic)
|
||||
// TODO g.fn_args(method.args[1..])
|
||||
methods_struct_def.writeln(');')
|
||||
}
|
||||
methods_struct_def.writeln('};')
|
||||
|
@ -6603,7 +6591,7 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype
|
|||
...params[0]
|
||||
typ: params[0].typ.set_nr_muls(1)
|
||||
}
|
||||
fargs, _, _ := g.fn_args(params, false, voidptr(0)) // second argument is ignored anyway
|
||||
fargs, _, _ := g.fn_args(params, voidptr(0))
|
||||
methods_wrapper.write_string(g.out.cut_last(g.out.len - params_start_pos))
|
||||
methods_wrapper.writeln(') {')
|
||||
methods_wrapper.write_string('\t')
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module c
|
||||
|
||||
import strings
|
||||
|
||||
// NB: @@@ here serve as placeholders.
|
||||
// They will be replaced with correct strings
|
||||
// for each constant, during C code generation.
|
||||
|
@ -49,6 +51,82 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) {
|
|||
}
|
||||
'
|
||||
|
||||
// Heavily based on Chris Wellons's work
|
||||
// https://nullprogram.com/blog/2017/01/08/
|
||||
|
||||
fn c_closure_helpers() string {
|
||||
$if windows {
|
||||
verror('closures are not implemented on Windows yet')
|
||||
}
|
||||
$if !x64 {
|
||||
verror('closures are not implemented on this architecture yet')
|
||||
}
|
||||
mut builder := strings.new_builder(1024)
|
||||
$if !windows {
|
||||
builder.writeln('#include <sys/mman.h>')
|
||||
}
|
||||
$if x64 {
|
||||
builder.write_string('
|
||||
static unsigned char __closure_thunk[6][13] = {
|
||||
{
|
||||
0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
}, {
|
||||
0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
}, {
|
||||
0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
}, {
|
||||
0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
}, {
|
||||
0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
}, {
|
||||
0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
|
||||
},
|
||||
};
|
||||
')
|
||||
}
|
||||
builder.write_string('
|
||||
static void __closure_set_data(void *closure, void *data) {
|
||||
void **p = closure;
|
||||
p[-2] = data;
|
||||
}
|
||||
|
||||
static void __closure_set_function(void *closure, void *f) {
|
||||
void **p = closure;
|
||||
p[-1] = f;
|
||||
}
|
||||
')
|
||||
$if !windows {
|
||||
builder.write_string('
|
||||
static void * __closure_create(void *f, int nargs, void *userdata) {
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
int prot = PROT_READ | PROT_WRITE;
|
||||
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
|
||||
char *p = mmap(0, page_size * 2, prot, flags, -1, 0);
|
||||
if (p == MAP_FAILED)
|
||||
return 0;
|
||||
void *closure = p + page_size;
|
||||
memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0]));
|
||||
mprotect(closure, page_size, PROT_READ | PROT_EXEC);
|
||||
__closure_set_function(closure, f);
|
||||
__closure_set_data(closure, userdata);
|
||||
return closure;
|
||||
}
|
||||
|
||||
static void __closure_destroy(void *closure) {
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
munmap((char *)closure - page_size, page_size * 2);
|
||||
}
|
||||
')
|
||||
}
|
||||
return builder.str()
|
||||
}
|
||||
|
||||
const c_common_macros = '
|
||||
#define EMPTY_VARG_INITIALIZATION 0
|
||||
#define EMPTY_STRUCT_DECLARATION
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// that can be found in the LICENSE file.
|
||||
module c
|
||||
|
||||
import strings
|
||||
import v.ast
|
||||
import v.util
|
||||
|
||||
|
@ -39,7 +40,6 @@ fn (mut g Gen) process_fn_decl(node ast.FnDecl) {
|
|||
return
|
||||
}
|
||||
g.gen_attrs(node.attrs)
|
||||
// g.tmp_count = 0 TODO
|
||||
mut skip := false
|
||||
pos := g.out.len
|
||||
should_bundle_module := util.should_bundle_module(node.mod)
|
||||
|
@ -166,6 +166,15 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
|||
g.table.cur_fn = node
|
||||
}
|
||||
fn_start_pos := g.out.len
|
||||
is_closure := node.scope.has_inherited_vars()
|
||||
mut cur_closure_ctx := ''
|
||||
if is_closure {
|
||||
cur_closure_ctx = closure_ctx_struct(node)
|
||||
// declare the struct before its implementation
|
||||
g.definitions.write_string(cur_closure_ctx)
|
||||
g.definitions.writeln(';')
|
||||
}
|
||||
|
||||
g.write_v_source_line_info(node.pos)
|
||||
msvc_attrs := g.write_fn_attrs(node.attrs)
|
||||
// Live
|
||||
|
@ -264,7 +273,19 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
|||
g.write(fn_header)
|
||||
}
|
||||
arg_start_pos := g.out.len
|
||||
fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.is_variadic, node.scope)
|
||||
fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.scope)
|
||||
if is_closure {
|
||||
mut s := '$cur_closure_ctx *$c.closure_ctx'
|
||||
if node.params.len > 0 {
|
||||
s = ', ' + s
|
||||
} else {
|
||||
// remove generated `void`
|
||||
g.out.cut_to(arg_start_pos)
|
||||
}
|
||||
g.definitions.write_string(s)
|
||||
g.write(s)
|
||||
g.nr_closures++
|
||||
}
|
||||
arg_str := g.out.after(arg_start_pos)
|
||||
if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin
|
||||
&& !g.is_test) || skip {
|
||||
|
@ -386,6 +407,59 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
|||
}
|
||||
}
|
||||
|
||||
const closure_ctx = '_V_closure_ctx'
|
||||
|
||||
fn closure_ctx_struct(node ast.FnDecl) string {
|
||||
return 'struct _V_${node.name}_Ctx'
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) {
|
||||
g.gen_anon_fn_decl(mut node)
|
||||
if !node.decl.scope.has_inherited_vars() {
|
||||
g.write(node.decl.name)
|
||||
return
|
||||
}
|
||||
// it may be possible to optimize `memdup` out if the closure never leaves current scope
|
||||
ctx_var := g.new_tmp_var()
|
||||
cur_line := g.go_before_stmt(0)
|
||||
ctx_struct := closure_ctx_struct(node.decl)
|
||||
g.writeln('$ctx_struct* $ctx_var = ($ctx_struct*) memdup(&($ctx_struct){')
|
||||
g.indent++
|
||||
for var in node.inherited_vars {
|
||||
g.writeln('.$var.name = $var.name,')
|
||||
}
|
||||
g.indent--
|
||||
g.writeln('}, sizeof($ctx_struct));')
|
||||
g.empty_line = false
|
||||
g.write(cur_line)
|
||||
// TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data)
|
||||
g.write('__closure_create($node.decl.name, ${node.decl.params.len + 1}, $ctx_var)')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
|
||||
if node.has_gen {
|
||||
return
|
||||
}
|
||||
node.has_gen = true
|
||||
mut builder := strings.new_builder(256)
|
||||
if node.inherited_vars.len > 0 {
|
||||
ctx_struct := closure_ctx_struct(node.decl)
|
||||
builder.writeln('$ctx_struct {')
|
||||
for var in node.inherited_vars {
|
||||
styp := g.typ(var.typ)
|
||||
builder.writeln('\t$styp $var.name;')
|
||||
}
|
||||
builder.writeln('};\n')
|
||||
}
|
||||
pos := g.out.len
|
||||
was_anon_fn := g.anon_fn
|
||||
g.anon_fn = true
|
||||
g.process_fn_decl(node.decl)
|
||||
g.anon_fn = was_anon_fn
|
||||
builder.write_string(g.out.cut_to(pos))
|
||||
g.anon_fn_definitions << builder.str()
|
||||
}
|
||||
|
||||
fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string {
|
||||
return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn'
|
||||
}
|
||||
|
@ -405,7 +479,7 @@ fn (mut g Gen) write_defer_stmts_when_needed() {
|
|||
}
|
||||
|
||||
// fn decl args
|
||||
fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([]string, []string, []bool) {
|
||||
fn (mut g Gen) fn_args(args []ast.Param, scope &ast.Scope) ([]string, []string, []bool) {
|
||||
mut fargs := []string{}
|
||||
mut fargtypes := []string{}
|
||||
mut heap_promoted := []bool{}
|
||||
|
@ -423,7 +497,7 @@ fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([]
|
|||
func := info.func
|
||||
g.write('${g.typ(func.return_type)} (*$caname)(')
|
||||
g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(')
|
||||
g.fn_args(func.params, func.is_variadic, voidptr(0))
|
||||
g.fn_args(func.params, voidptr(0))
|
||||
g.write(')')
|
||||
g.definitions.write_string(')')
|
||||
fargs << caname
|
||||
|
|
|
@ -633,9 +633,11 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
|
|||
old_inside_defer := p.inside_defer
|
||||
p.inside_defer = false
|
||||
p.open_scope()
|
||||
if !p.pref.backend.is_js() {
|
||||
p.scope.detached_from_parent = true
|
||||
defer {
|
||||
p.close_scope()
|
||||
}
|
||||
p.scope.detached_from_parent = true
|
||||
inherited_vars := if p.tok.kind == .lsbr { p.closure_vars() } else { []ast.Param{} }
|
||||
// TODO generics
|
||||
args, _, is_variadic := p.fn_args()
|
||||
for arg in args {
|
||||
|
@ -691,7 +693,6 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
|
|||
p.label_names = tmp
|
||||
}
|
||||
p.cur_fn_name = keep_fn_name
|
||||
p.close_scope()
|
||||
func.name = name
|
||||
idx := p.table.find_or_register_fn_type(p.mod, func, true, false)
|
||||
typ := ast.new_type(idx)
|
||||
|
@ -714,6 +715,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
|
|||
scope: p.scope
|
||||
label_names: label_names
|
||||
}
|
||||
inherited_vars: inherited_vars
|
||||
typ: typ
|
||||
}
|
||||
}
|
||||
|
@ -910,6 +912,50 @@ fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) {
|
|||
return args, types_only, is_variadic
|
||||
}
|
||||
|
||||
fn (mut p Parser) closure_vars() []ast.Param {
|
||||
p.check(.lsbr)
|
||||
mut vars := []ast.Param{cap: 5}
|
||||
for {
|
||||
is_shared := p.tok.kind == .key_shared
|
||||
is_atomic := p.tok.kind == .key_atomic
|
||||
is_mut := p.tok.kind == .key_mut || is_shared || is_atomic
|
||||
// FIXME is_shared & is_atomic aren't used further
|
||||
if is_mut {
|
||||
p.next()
|
||||
}
|
||||
var_pos := p.tok.position()
|
||||
p.check(.name)
|
||||
var_name := p.prev_tok.lit
|
||||
mut var := p.scope.parent.find_var(var_name) or {
|
||||
p.error_with_pos('undefined ident: `$var_name`', p.prev_tok.position())
|
||||
continue
|
||||
}
|
||||
var.is_used = true
|
||||
if is_mut {
|
||||
var.is_changed = true
|
||||
}
|
||||
p.scope.register(ast.Var{
|
||||
...(*var)
|
||||
pos: var_pos
|
||||
is_inherited: true
|
||||
is_used: false
|
||||
is_changed: false
|
||||
is_mut: is_mut
|
||||
})
|
||||
vars << ast.Param{
|
||||
pos: var_pos
|
||||
name: var_name
|
||||
is_mut: is_mut
|
||||
}
|
||||
if p.tok.kind != .comma {
|
||||
break
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
p.check(.rsbr)
|
||||
return vars
|
||||
}
|
||||
|
||||
fn (mut p Parser) check_fn_mutable_arguments(typ ast.Type, pos token.Position) {
|
||||
sym := p.table.get_type_symbol(typ)
|
||||
if sym.kind in [.array, .array_fixed, .interface_, .map, .placeholder, .struct_, .generic_inst,
|
||||
|
|
|
@ -1784,49 +1784,49 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident {
|
|||
if is_static {
|
||||
p.next()
|
||||
}
|
||||
if p.tok.kind == .name {
|
||||
pos := p.tok.position()
|
||||
mut name := p.check_name()
|
||||
if name == '_' {
|
||||
return ast.Ident{
|
||||
tok_kind: p.tok.kind
|
||||
name: '_'
|
||||
comptime: p.comp_if_cond
|
||||
kind: .blank_ident
|
||||
pos: pos
|
||||
info: ast.IdentVar{
|
||||
is_mut: false
|
||||
is_static: false
|
||||
}
|
||||
scope: p.scope
|
||||
}
|
||||
}
|
||||
if p.inside_match_body && name == 'it' {
|
||||
// p.warn('it')
|
||||
}
|
||||
if p.expr_mod.len > 0 {
|
||||
name = '${p.expr_mod}.$name'
|
||||
if p.tok.kind != .name {
|
||||
p.error('unexpected token `$p.tok.lit`')
|
||||
return ast.Ident{
|
||||
scope: p.scope
|
||||
}
|
||||
}
|
||||
pos := p.tok.position()
|
||||
mut name := p.check_name()
|
||||
if name == '_' {
|
||||
return ast.Ident{
|
||||
tok_kind: p.tok.kind
|
||||
kind: .unresolved
|
||||
name: name
|
||||
name: '_'
|
||||
comptime: p.comp_if_cond
|
||||
language: language
|
||||
mod: p.mod
|
||||
kind: .blank_ident
|
||||
pos: pos
|
||||
is_mut: is_mut
|
||||
mut_pos: mut_pos
|
||||
info: ast.IdentVar{
|
||||
is_mut: is_mut
|
||||
is_static: is_static
|
||||
share: ast.sharetype_from_flags(is_shared, is_atomic)
|
||||
is_mut: false
|
||||
is_static: false
|
||||
}
|
||||
scope: p.scope
|
||||
}
|
||||
}
|
||||
p.error('unexpected token `$p.tok.lit`')
|
||||
if p.inside_match_body && name == 'it' {
|
||||
// p.warn('it')
|
||||
}
|
||||
if p.expr_mod.len > 0 {
|
||||
name = '${p.expr_mod}.$name'
|
||||
}
|
||||
return ast.Ident{
|
||||
tok_kind: p.tok.kind
|
||||
kind: .unresolved
|
||||
name: name
|
||||
comptime: p.comp_if_cond
|
||||
language: language
|
||||
mod: p.mod
|
||||
pos: pos
|
||||
is_mut: is_mut
|
||||
mut_pos: mut_pos
|
||||
info: ast.IdentVar{
|
||||
is_mut: is_mut
|
||||
is_static: is_static
|
||||
share: ast.sharetype_from_flags(is_shared, is_atomic)
|
||||
}
|
||||
scope: p.scope
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
vlib/v/parser/tests/closure_not_declared.vv:4:9: error: undefined ident: `a`
|
||||
2 | a := 1
|
||||
3 | f := fn () {
|
||||
4 | print(a)
|
||||
| ^
|
||||
5 | }
|
||||
6 | f()
|
|
@ -0,0 +1,8 @@
|
|||
fn my_fn() {
|
||||
a := 1
|
||||
f := fn () {
|
||||
print(a)
|
||||
}
|
||||
f()
|
||||
_ = a
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
vlib/v/parser/tests/closure_not_used.vv:3:11: error: unused variable: `a`
|
||||
1 | fn my_fn() {
|
||||
2 | a := 1
|
||||
3 | f := fn [a] () {
|
||||
| ^
|
||||
4 | print("hello")
|
||||
5 | }
|
|
@ -0,0 +1,7 @@
|
|||
fn my_fn() {
|
||||
a := 1
|
||||
f := fn [a] () {
|
||||
print("hello")
|
||||
}
|
||||
f()
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
vlib/v/parser/tests/closure_undefined_var.vv:2:11: error: undefined ident: `dont_exist`
|
||||
1 | fn my_fn() {
|
||||
2 | f := fn [dont_exist] () {
|
||||
| ~~~~~~~~~~
|
||||
3 | print(dont_exist)
|
||||
4 | }
|
|
@ -0,0 +1,6 @@
|
|||
fn my_fn() {
|
||||
f := fn [dont_exist] () {
|
||||
print(dont_exist)
|
||||
}
|
||||
f()
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
fn test_decl_assignment() {
|
||||
my_var := 12
|
||||
c1 := fn [my_var] () int {
|
||||
return my_var
|
||||
}
|
||||
assert c1() == 12
|
||||
}
|
||||
|
||||
fn test_assignment() {
|
||||
v1 := 1
|
||||
mut c := fn [v1] () int {
|
||||
return v1
|
||||
}
|
||||
v2 := 3
|
||||
c = fn [v2] () int {
|
||||
return v2
|
||||
}
|
||||
assert c() == 3
|
||||
}
|
||||
|
||||
fn call_anon_with_3(f fn (int) int) int {
|
||||
return f(3)
|
||||
}
|
||||
|
||||
fn test_closure_in_call() {
|
||||
my_var := int(12)
|
||||
r1 := call_anon_with_3(fn [my_var] (add int) int {
|
||||
return my_var + add
|
||||
})
|
||||
assert r1 == 15
|
||||
}
|
||||
|
||||
fn create_simple_counter() fn () int {
|
||||
mut c := -1
|
||||
return fn [mut c] () int {
|
||||
c++
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
fn override_stack() {
|
||||
// just create some variables to modify the stack
|
||||
a := 1
|
||||
b := a + 2
|
||||
c := b + 3
|
||||
d := c + 4
|
||||
e := d + 5
|
||||
f := e + 6
|
||||
g := f + 7
|
||||
_ = g
|
||||
}
|
||||
|
||||
fn test_closure_exit_original_scope() {
|
||||
mut c := create_simple_counter()
|
||||
assert c() == 0
|
||||
override_stack()
|
||||
assert c() == 1
|
||||
}
|
||||
|
||||
fn test_vars_are_changed() {
|
||||
mut my_var := 1
|
||||
f1 := fn [mut my_var] (expected int) {
|
||||
assert my_var == expected
|
||||
}
|
||||
f1(1)
|
||||
my_var = 3
|
||||
f1(1)
|
||||
my_var = -1
|
||||
f1(1)
|
||||
}
|
||||
|
||||
struct Counter {
|
||||
mut:
|
||||
i u64
|
||||
}
|
||||
|
||||
fn (mut c Counter) incr() {
|
||||
c.i++
|
||||
}
|
||||
|
||||
fn (mut c Counter) next() u64 {
|
||||
c.incr()
|
||||
return c.i
|
||||
}
|
||||
|
||||
fn test_call_methods() {
|
||||
mut c := Counter{}
|
||||
f1 := fn [mut c] () u64 {
|
||||
return c.next()
|
||||
}
|
||||
assert f1() == 1
|
||||
assert f1() == 2
|
||||
c.incr()
|
||||
assert f1() == 3
|
||||
}
|
||||
|
||||
fn test_call_methods_on_pointer() {
|
||||
mut c := &Counter{}
|
||||
f1 := fn [mut c] () u64 {
|
||||
return c.next()
|
||||
}
|
||||
assert f1() == 1
|
||||
assert f1() == 2
|
||||
c.incr()
|
||||
assert f1() == 4
|
||||
}
|
||||
|
||||
/*
|
||||
fn test_methods_as_variables() {
|
||||
mut c := Counter{}
|
||||
f1 := c.next
|
||||
assert f1() == 1
|
||||
assert f1() == 2
|
||||
c.incr()
|
||||
assert f1() == 3
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
fn takes_callback(get fn () u64) u64 {
|
||||
return get()
|
||||
}
|
||||
|
||||
fn test_methods_as_callback() {
|
||||
mut c := Counter{}
|
||||
assert takes_callback(c.next) == 1
|
||||
}
|
||||
*/
|
||||
|
||||
struct ZeroSize {}
|
||||
|
||||
fn test_zero_size_ctx() {
|
||||
ctx := ZeroSize{}
|
||||
c1 := fn [ctx] () int {
|
||||
return 123
|
||||
}
|
||||
assert c1() == 123
|
||||
}
|
||||
|
||||
/*
|
||||
fn test_go_call_closure() {
|
||||
my_var := 12
|
||||
ch := chan int{}
|
||||
go fn [my_var, ch] () {
|
||||
ch <- my_var
|
||||
}()
|
||||
assert <-ch == 12
|
||||
go fn [ch] (arg int) {
|
||||
ch <- arg
|
||||
}(15)
|
||||
assert <-ch == 15
|
||||
}
|
||||
*/
|
|
@ -13,10 +13,6 @@ pub mut:
|
|||
last_line int // the line number where the ast object ends (used by vfmt)
|
||||
}
|
||||
|
||||
pub fn (pos Position) str() string {
|
||||
return 'Position{ line_nr: $pos.line_nr, last_line: $pos.last_line, pos: $pos.pos, col: $pos.col, len: $pos.len }'
|
||||
}
|
||||
|
||||
pub fn (pos Position) extend(end Position) Position {
|
||||
return Position{
|
||||
...pos
|
||||
|
@ -29,9 +25,9 @@ pub fn (pos Position) extend_with_last_line(end Position, last_line int) Positio
|
|||
return Position{
|
||||
len: end.pos - pos.pos + end.len
|
||||
line_nr: pos.line_nr
|
||||
last_line: last_line - 1
|
||||
pos: pos.pos
|
||||
col: pos.col
|
||||
last_line: last_line - 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue