diff --git a/fec/src/check.c b/fec/src/check.c index 4c6ae41..5da3711 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -70,9 +70,27 @@ static int known(FeType *t) return t && t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_ERROR; } +/* Is this a projection of `self` inside that type's own `drop`? */ +static int in_own_drop(FeCheckerState *s, FeNode *n) +{ + FeNode *base; + if (!s->fn_node || !s->fn_node->text || strcmp(s->fn_node->text,"drop")!=0) + return 0; + base = n ? n->a : 0; + while (base && (base->kind==FE_N_MEMBER || base->kind==FE_N_INDEX)) + base = base->a; + return base && base->kind==FE_N_IDENT && base->text && + strcmp(base->text,"self")==0; +} + static void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) { FeSym *sym=0; + /* Inside a type's own `drop` the object is going away, so taking a field + out of it leaves nothing behind that anyone could read. That is the one + place R7 has nothing to protect. */ + if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX) && in_own_drop(s,n)) + return; if (n && n->kind==FE_N_IDENT) sym=find_symbol(s->scope,n->text ? n->text : ""); if (s->defer_depth != 0) { @@ -1283,6 +1301,12 @@ static FeType *check_expr_core(FeCheckerState *s, FeNode *n) } } et=check_expr(s,n->a->a); + /* A method can be reached through a reference or an owner as well + as through the value itself. */ + if (et && (et->kind==FE_TYPE_REF || et->kind==FE_TYPE_OWNED) && + et->elem && et->elem->kind==FE_TYPE_STRUCT && + find_method(c,et->elem,n->a->b ? n->a->b->text : "")) + et=et->elem; method=et && et->kind==FE_TYPE_STRUCT ? find_method(c,et,n->a->b ? n->a->b->text : "") : 0; if(method) { @@ -2361,6 +2385,23 @@ static FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, pop_bindings(c,&save); } fe_type_layout_all(&c->types); + /* A type that says how to let go of itself needs that method to exist for + every instance, whether or not anyone calls it by name: scope cleanup + will. */ + { + FeNode *release; + for (release=decl->children;release;release=release->next) + if (release->kind==FE_N_FN && release->text && + !strcmp(release->text,"drop") && release->c) { + FeCheckerState s; + memset(&s,0,sizeof s); + s.c=c; + s.scope=c->unit_scope[unit_index(c,home)]; + s.globals=s.scope; + check_instance_method(&s,t,release,decl->loc,0); + break; + } + } return t; } @@ -2391,18 +2432,30 @@ static FeType *instantiate_type_node(void *owner, const FeNode *node) { FeCheck *c=(FeCheck *)owner; FeUnit *home=current_unit(c); + const char *name=node->text; FeNode *arg; FeType *args[FE_TYPE_PARAM_MAX]; unsigned count=0; FeType *result; + /* `binding.Name` names a type in another unit. The binding is not itself a + type, so it has to be peeled off before anything is looked up. */ + if (node->a && node->a->kind==FE_N_IDENT && node->a->text && c->build && + c->unit) { + FeUnit *bound=fe_build_binding(c->build,c->unit,node->text); + if (bound) { home=bound; name=node->a->text; } + } if (!node->children) { - /* A generic declaration is not a type until it has its arguments. */ - FeNode *decl=unit_type_decl(c,home,node->text ? node->text : ""); + FeNode *decl=unit_type_decl(c,home,name ? name : ""); if (decl && decl_is_generic(decl)) { + /* A generic declaration is not a type until it has arguments. */ err(c,node->loc,"generic type requires type arguments"); return unknown(c); } - return fe_type_intern(&c->types,node->text); + if (name!=node->text) { + FeType *there=unit_type(c,home,name); + if (there) return there; + } + return fe_type_intern(&c->types,name); } if (!instance_descend(c,node->loc)) return unknown(c); for (arg=node->children;arg;arg=arg->next) { @@ -2415,7 +2468,7 @@ static FeType *instantiate_type_node(void *owner, const FeNode *node) --c->instance_depth; return unknown(c); } - result=instantiate_struct(c,home,node->text ? node->text : "",args,count, + result=instantiate_struct(c,home,name ? name : "",args,count, node->loc); --c->instance_depth; return result; @@ -2448,13 +2501,27 @@ static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) if (t && t->kind!=FE_TYPE_UNKNOWN) { *ok=1; return t; } return unknown(c); } - if (n->kind==FE_N_CALL && n->a && n->a->kind==FE_N_IDENT && n->a->text) { + if (n->kind==FE_N_CALL && n->a && + (n->a->kind==FE_N_IDENT || + (n->a->kind==FE_N_MEMBER && n->a->a && + n->a->a->kind==FE_N_IDENT && n->a->b && n->a->b->text))) { FeType *args[FE_TYPE_PARAM_MAX]; unsigned count=0; FeNode *arg; FeType *result; FeUnit *home=current_unit(c); - if (!unit_type_decl(c,home,n->a->text)) return unknown(c); + const char *want; + /* `Name(args)` here, `binding.Name(args)` when the declaration is in + another unit. */ + if (n->a->kind==FE_N_MEMBER) { + FeUnit *bound=binding_unit(s,n->a->a); + if (!bound) return unknown(c); + home=bound; + want=n->a->b->text; + } else { + want=n->a->text; + } + if (!want || !unit_type_decl(c,home,want)) return unknown(c); if (!instance_descend(c,n->loc)) { *ok=1; return unknown(c); } for (arg=n->children;arg;arg=arg->next) { int inner=0; @@ -2464,7 +2531,7 @@ static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) ++count; } if (count>FE_TYPE_PARAM_MAX) { --c->instance_depth; return unknown(c); } - result=instantiate_struct(c,home,n->a->text,args,count,n->loc); + result=instantiate_struct(c,home,want,args,count,n->loc); --c->instance_depth; *ok=1; return result; @@ -2578,17 +2645,30 @@ static FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, /* `Type.method(...)` where Type is a generic instance and the method takes no self parameter. */ +/* The unit a name belongs to, by name. */ +static FeUnit *unit_named(FeCheck *c, const char *name) +{ + unsigned u; + if (!name) return 0; + for (u=0;ubuild->count;++u) + if (!strcmp(c->build->units[u].name,name)) return &c->build->units[u]; + return 0; +} + static FeType *check_static_method_call(FeCheckerState *s, FeNode *n, FeType *owner, FeNode *method) { FeCheck *c=s->c; - FeUnit *home=current_unit(c); + /* A method belongs to the unit that declared its type, not to whichever + unit happens to be calling it. */ + FeUnit *home=unit_named(c,owner ? owner->unit : 0); FeBindSave save; FeType *result; char key[FE_GENERIC_KEY_MAX]; FeType *self_args[1]; int fresh; FeSym fake; + if (!home) home=current_unit(c); self_args[0]=owner; instance_key(key,home->name,method->text,self_args,1); memset(&fake,0,sizeof fake); @@ -2618,11 +2698,14 @@ static void check_instance_method(FeCheckerState *s, FeType *owner, FeNode *method, FeLoc site, FeNode *call) { FeCheck *c=s->c; - FeUnit *home=current_unit(c); + /* A method belongs to the unit that declared its type, not to whichever + unit happens to be calling it. */ + FeUnit *home=unit_named(c,owner ? owner->unit : 0); FeBindSave save; char key[FE_GENERIC_KEY_MAX]; FeType *self_args[1]; self_args[0]=owner; + if (!home) home=current_unit(c); instance_key(key,home->name,method->text,self_args,1); { FeBindSave probe; diff --git a/fec/src/lower.c b/fec/src/lower.c index e6ad699..07c2e08 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -687,9 +687,17 @@ static Slot lower_call(Lower *L, FeNode *n) if (n->a && n->a->kind == FE_N_MEMBER && n->sem_decl) { FeNode *first = n->sem_decl->a ? n->sem_decl->a->children : 0; if (first && first->text && !strcmp(first->text, "self")) { + FeType *rt = n->a->a ? n->a->a->sem_type : 0; Slot recv = lower_expr(L, n->a->a); - args[count++] = recv.is_place ? as_address(L, recv, n->a->a) - : recv.temp; + /* A receiver that is already a reference or an owner is a pointer + already; taking its address would pass a pointer to the + pointer. */ + if (rt && (rt->kind == FE_TYPE_REF || + (rt->kind == FE_TYPE_OWNED && ir_type(rt) == FE_IR_PTR))) + args[count++] = as_value(L, recv, n->a->a); + else + args[count++] = recv.is_place ? as_address(L, recv, n->a->a) + : recv.temp; } } /* A generic call passes its type arguments first. They were consumed when @@ -990,6 +998,24 @@ static Slot lower_expr_core(Lower *L, FeNode *n) } } +/* The link name of the `drop` method for this type, found through the instance + the checker recorded. */ +static const char *drop_name(Lower *L, const FeType *t) +{ + unsigned i; + FeNode *method = 0; + if (!t || !t->decl_node) return 0; + for (method = t->decl_node->children; method; method = method->next) + if (method->kind == FE_N_FN && method->text && + !strcmp(method->text, "drop")) break; + if (!method) return 0; + for (i = 0; i < L->c->instance_count; ++i) + if (L->c->instances[i].decl == method && + L->c->instances[i].owner == t) + return L->c->instances[i].cname; + return method->cname; +} + /* Settle what a scope owes, most recent first. A `return` in the middle of a function still owes everything, so every exit path calls this. */ static void run_deferred(Lower *L, unsigned from) @@ -1019,7 +1045,16 @@ static void run_deferred(Lower *L, unsigned from) args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, fe_ir_at_local(L->owed[i - 1].local, 0)); } - fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1); + if (t && t->has_drop) { + /* A type that says how to let go of itself is asked to; the + name is the one its instance was given. */ + const char *how = drop_name(L, t); + args[0] = fe_ir_addr(L->m, L->b, + fe_ir_at_local(L->owed[i - 1].local, 0)); + if (how) fe_ir_call(L->m, L->b, FE_IR_VOID, how, args, 1); + } else { + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1); + } fe_ir_jmp(L->b, skip->id); L->b = skip; } diff --git a/fec/std/list.fe b/fec/std/list.fe index d414df7..58fff67 100644 --- a/fec/std/list.fe +++ b/fec/std/list.fe @@ -1,6 +1,53 @@ -unit list; +unit std.list; + +// A growable sequence. The buffer is owned, so a List owns its elements and +// releasing it releases them (SPEC 5 R1). Growth doubles, which keeps the +// total copying proportional to the number of pushes. + pub struct List(T) { - items: ^[]T, + items: ^[]mut T, len: usize, - pub fn at(self: &Self, i: usize) -> &T; + + pub fn with_capacity(n: usize) -> !Self { + let room: ^[]mut T = try mem.alloc_slice(T, n); + return Self{ items: room, len: 0 }; + } + + pub fn count(self: &Self) -> usize { return self.len; } + + pub fn at(self: &Self, i: usize) -> T { + return self.items.^[i]; + } + + pub fn set(self: &mut Self, i: usize, v: T) -> void { + self.items.^[i] = v; + } + + pub fn push(self: &mut Self, v: T) -> !void { + if self.len == self.items.^.n { try self.grow(); } + self.items.^[self.len] = v; + self.len = self.len + 1; + return; + } + + /// Move to a buffer twice the size. Kept apart from `push` because the + /// borrow that hands over the old buffer must not be live while the old + /// buffer is still being read (SPEC 5 R6). + fn grow(self: &mut Self) -> !void { + var room: usize = self.items.^.n * 2; + if room == 0 { room = 4; } + let bigger: ^[]mut T = try mem.alloc_slice(T, room); + var i: usize = 0; + while i < self.len { + bigger.^[i] = self.items.^[i]; + i = i + 1; + } + let old: ^[]mut T = mem.replace(&mut self.items, bigger); + mem.destroy(old); + return; + } + + pub fn drop(self: &mut Self) -> void { + mem.destroy(self.items); + } } diff --git a/fec/std/str.fe b/fec/std/str.fe index 4d5d7f0..0e4dbc5 100644 --- a/fec/std/str.fe +++ b/fec/std/str.fe @@ -1,3 +1,66 @@ -unit str; -pub fn eq(a: str, b: str) -> bool; -pub fn trim(s: str) -> str; +unit std.str; + +// `str` is `[]u8` (SPEC 4.2), so these take and give plain byte slices. + +pub fn eq(a: []u8, b: []u8) -> bool { + if a.n != b.n { return false; } + var i: usize = 0; + while i < a.n { + if a[i] != b[i] { return false; } + i = i + 1; + } + return true; +} + +pub fn starts_with(s: []u8, prefix: []u8) -> bool { + if prefix.n > s.n { return false; } + return eq(s[0..prefix.n], prefix); +} + +/// Where `needle` first appears in `s`, or the length of `s` when it does not. +/// An index past the end is how "not found" is said without an optional. +pub fn find(s: []u8, needle: []u8) -> usize { + if needle.n == 0 { return 0; } + if needle.n > s.n { return s.n; } + var at: usize = 0; + let last: usize = s.n - needle.n; + while at <= last { + if eq(s[at..at + needle.n], needle) { return at; } + at = at + 1; + } + return s.n; +} + +pub fn trim(s: []u8) -> []u8 { + var from: usize = 0; + var to: usize = s.n; + while from < to { + if s[from] != 32 and s[from] != 9 and s[from] != 10 and s[from] != 13 { + break; + } + from = from + 1; + } + while to > from { + let c: u8 = s[to - 1]; + if c != 32 and c != 9 and c != 10 and c != 13 { break; } + to = to - 1; + } + return s[from..to]; +} + +pub fn parse_int(s: []u8) -> ?i32 { + if s.n == 0 { return null; } + var value: i32 = 0; + var i: usize = 0; + var negative: bool = false; + if s[0] == 45 { negative = true; i = 1; } + if i >= s.n { return null; } + while i < s.n { + let c: u8 = s[i]; + if c < 48 or c > 57 { return null; } + value = value * 10 + ((c - 48) as i32); + i = i + 1; + } + if negative { return 0 - value; } + return value; +} diff --git a/fec/tests/exec/listuse.fe b/fec/tests/exec/listuse.fe new file mode 100644 index 0000000..2c0d703 --- /dev/null +++ b/fec/tests/exec/listuse.fe @@ -0,0 +1,42 @@ +// EXIT:0 +// OUTPUT:count 20 sum 190 balanced +unit listuse; +import std.io; +import std.fmt; +import std.list; +import std.sys; + +fn show(label: []u8, v: i32) -> void { + var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let n: usize = fmt.fmt_i32(buf[..], v); + io.print(label); + io.print(" "); + io.print(buf[0..n]); + io.print(" "); +} + +fn build() -> !i32 { + var xs: list.List(i32) = try list.List(i32).with_capacity(2); + var i: i32 = 0; + while i < 20 { + try xs.push(i); + i = i + 1; + } + var sum: i32 = 0; + var k: usize = 0; + while k < xs.count() { + sum = sum + xs.at(k); + k = k + 1; + } + show("count", xs.count() as i32); + show("sum", sum); + return sum; +} + +fn main() -> i32 { + let sum: i32 = build() catch |e| { return 1; }; + if sum != 190 { return 2; } + if sys.allocs() != sys.frees() { io.print("leaked\n"); return 3; } + io.print("balanced\n"); + return 0; +} diff --git a/fec/tests/exec/strings.fe b/fec/tests/exec/strings.fe new file mode 100644 index 0000000..fb2afb9 --- /dev/null +++ b/fec/tests/exec/strings.fe @@ -0,0 +1,29 @@ +// EXIT:0 +// OUTPUT:ok 42 -7 +unit strings; +import std.io; +import std.str; +import std.fmt; + +fn show(v: i32) -> void { + var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let n: usize = fmt.fmt_i32(buf[..], v); + io.print(" "); + io.print(buf[0..n]); +} + +fn main() -> i32 { + if not str.eq(str.trim(" hello "), "hello") { return 1; } + if not str.starts_with("ferro", "fer") { return 2; } + if str.find("abcdef", "cd") != 2 { return 3; } + if str.find("abcdef", "zz") != 6 { return 4; } + let a: i32 = str.parse_int("42") orelse 0; + let b: i32 = str.parse_int("-7") orelse 0; + let bad: i32 = str.parse_int("12x") orelse 0; + if bad != 0 { return 5; } + io.print("ok"); + show(a); + show(b); + io.print("\n"); + return 0; +}