mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
boot: defer, str.bytes()
defer runs when its scope ends and before return, break and continue leave it, newest first, and sees only the variables declared before it. Not allowed inside a defer: defer, return, break, continue. boot.sh: 14 passed, 0 failed, 8 skipped.
This commit is contained in:
parent
051af1a04e
commit
84693aa655
3 changed files with 96 additions and 9 deletions
90
boot/gen.fch
90
boot/gen.fch
|
|
@ -10,15 +10,21 @@ struct Var {
|
||||||
ast.Type ty
|
ast.Type ty
|
||||||
str slot
|
str slot
|
||||||
bool readonly
|
bool readonly
|
||||||
|
int order // how many variables were declared before this one (for defer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A scope's variables, and the defers that run when it ends, newest first.
|
||||||
|
// A defer sees only the variables declared before it: `orders` says how many.
|
||||||
struct Scope {
|
struct Scope {
|
||||||
[]Var vars
|
[]Var vars
|
||||||
|
[]ast.Node defers
|
||||||
|
[]int orders
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Loop {
|
struct Loop {
|
||||||
str cont
|
str cont
|
||||||
str brk
|
str brk
|
||||||
|
int depth // the scope depth at the loop: break and continue unwind down to it
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FnSig {
|
struct FnSig {
|
||||||
|
|
@ -83,6 +89,10 @@ struct Gen {
|
||||||
[]StructDef structs
|
[]StructDef structs
|
||||||
[]str done
|
[]str done
|
||||||
[]str mm // names of methods that change self, for the mutation analysis
|
[]str mm // names of methods that change self, for the mutation analysis
|
||||||
|
int nvars // variables declared so far in this function
|
||||||
|
bool deferOn // running a defer: variables declared after it are out of sight
|
||||||
|
int deferLimit
|
||||||
|
int inDefer
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- basics ----------
|
// ---------- basics ----------
|
||||||
|
|
@ -776,6 +786,9 @@ fn lookup(ptr[Gen] g, str name) -> int {
|
||||||
s := g.scopes.len - 1 - k
|
s := g.scopes.len - 1 - k
|
||||||
for i in 0..g.scopes[s].vars.len {
|
for i in 0..g.scopes[s].vars.len {
|
||||||
if g.scopes[s].vars[i].name == name {
|
if g.scopes[s].vars[i].name == name {
|
||||||
|
if g.deferOn && g.scopes[s].vars[i].order >= g.deferLimit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return s * 100000 + i
|
return s * 100000 + i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -824,7 +837,8 @@ fn addVar(ptr[Gen] g, ast.Node at, str name, ast.Type t, str init, bool readonly
|
||||||
}
|
}
|
||||||
slot := alloca(g, llty(g, t))
|
slot := alloca(g, llty(g, t))
|
||||||
store(g, llty(g, t), init, slot)
|
store(g, llty(g, t), init, slot)
|
||||||
g.scopes[g.scopes.len - 1].vars.push(Var(name: name, ty: t, slot: slot, readonly: readonly))
|
g.scopes[g.scopes.len - 1].vars.push(Var(name: name, ty: t, slot: slot, readonly: readonly, order: g.nvars))
|
||||||
|
g.nvars += 1
|
||||||
return slot
|
return slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -836,6 +850,43 @@ fn popScope(ptr[Gen] g) {
|
||||||
g.scopes.pop()
|
g.scopes.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the defers of scope `idx`, newest first, as if written where the scope ends.
|
||||||
|
fn runDefers(ptr[Gen] g, int idx) {
|
||||||
|
sc := g.scopes[idx]
|
||||||
|
k := sc.defers.len
|
||||||
|
while k > 0 {
|
||||||
|
k -= 1
|
||||||
|
savedOn := g.deferOn
|
||||||
|
savedLimit := g.deferLimit
|
||||||
|
g.deferOn = true
|
||||||
|
g.deferLimit = sc.orders[k]
|
||||||
|
g.inDefer += 1
|
||||||
|
pushScope(g)
|
||||||
|
stmt(g, sc.defers[k])
|
||||||
|
popScope(g)
|
||||||
|
g.inDefer -= 1
|
||||||
|
g.deferOn = savedOn
|
||||||
|
g.deferLimit = savedLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The defers of every scope from the innermost down to `depth`, for return, break and continue.
|
||||||
|
fn unwind(ptr[Gen] g, int depth) {
|
||||||
|
i := g.scopes.len
|
||||||
|
while i > depth {
|
||||||
|
i -= 1
|
||||||
|
runDefers(g, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end of a block: its defers run, unless the block has already left by return, break or continue.
|
||||||
|
fn endScope(ptr[Gen] g) {
|
||||||
|
if !g.term {
|
||||||
|
runDefers(g, g.scopes.len - 1)
|
||||||
|
}
|
||||||
|
g.scopes.pop()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- mutation analysis: does a function change a parameter? ----------
|
// ---------- mutation analysis: does a function change a parameter? ----------
|
||||||
|
|
||||||
fn rootVar(ast.Node e) -> str {
|
fn rootVar(ast.Node e) -> str {
|
||||||
|
|
@ -992,6 +1043,9 @@ fn define(ptr[Gen] g, int si) {
|
||||||
g.allocas = empty
|
g.allocas = empty
|
||||||
g.tmp = 0
|
g.tmp = 0
|
||||||
g.term = false
|
g.term = false
|
||||||
|
g.nvars = 0
|
||||||
|
g.deferOn = false
|
||||||
|
g.inDefer = 0
|
||||||
[]Scope none
|
[]Scope none
|
||||||
g.scopes = none
|
g.scopes = none
|
||||||
pushScope(g)
|
pushScope(g)
|
||||||
|
|
@ -1015,6 +1069,7 @@ fn define(ptr[Gen] g, int si) {
|
||||||
if g.cur.ret.kind != "void" {
|
if g.cur.ret.kind != "void" {
|
||||||
fail(g, f, "function '" + f.name + "' can reach its end without returning " + ast.show(g.cur.ret))
|
fail(g, f, "function '" + f.name + "' can reach its end without returning " + ast.show(g.cur.ret))
|
||||||
}
|
}
|
||||||
|
runDefers(g, 0)
|
||||||
emit(g, "ret void")
|
emit(g, "ret void")
|
||||||
}
|
}
|
||||||
g.out.push("define internal " + llty(g, g.cur.ret) + " " + g.cur.ll + "(" + params.join(", ") + ") {\nentry:\n" +
|
g.out.push("define internal " + llty(g, g.cur.ret) + " " + g.cur.ll + "(" + params.join(", ") + ") {\nentry:\n" +
|
||||||
|
|
@ -1034,7 +1089,7 @@ fn block(ptr[Gen] g, ast.Node b, bool scope) {
|
||||||
stmt(g, s)
|
stmt(g, s)
|
||||||
}
|
}
|
||||||
if scope {
|
if scope {
|
||||||
popScope(g)
|
endScope(g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1058,11 +1113,22 @@ fn stmt(ptr[Gen] g, ast.Node s) {
|
||||||
eachStmt(g, s)
|
eachStmt(g, s)
|
||||||
} else if k == "return" {
|
} else if k == "return" {
|
||||||
returnStmt(g, s)
|
returnStmt(g, s)
|
||||||
|
} else if k == "defer" {
|
||||||
|
if g.inDefer > 0 {
|
||||||
|
fail(g, s, "defer inside defer is not allowed")
|
||||||
|
}
|
||||||
|
sc := g.scopes.len - 1
|
||||||
|
g.scopes[sc].defers.push(s.kids[0])
|
||||||
|
g.scopes[sc].orders.push(g.nvars)
|
||||||
} else if k == "break" || k == "continue" {
|
} else if k == "break" || k == "continue" {
|
||||||
|
if g.inDefer > 0 {
|
||||||
|
fail(g, s, k + " can't be used inside defer")
|
||||||
|
}
|
||||||
if g.loops.len == 0 {
|
if g.loops.len == 0 {
|
||||||
fail(g, s, k + " can only be used inside a loop")
|
fail(g, s, k + " can only be used inside a loop")
|
||||||
}
|
}
|
||||||
l := g.loops[g.loops.len - 1]
|
l := g.loops[g.loops.len - 1]
|
||||||
|
unwind(g, l.depth)
|
||||||
if k == "break" {
|
if k == "break" {
|
||||||
br(g, l.brk)
|
br(g, l.brk)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1187,7 +1253,7 @@ fn whileStmt(ptr[Gen] g, ast.Node s) {
|
||||||
condBr(g, c, body, end)
|
condBr(g, c, body, end)
|
||||||
}
|
}
|
||||||
label(g, body)
|
label(g, body)
|
||||||
g.loops.push(Loop(cont: cond, brk: end))
|
g.loops.push(Loop(cont: cond, brk: end, depth: g.scopes.len))
|
||||||
breaks := countBreaks(s.kids[1])
|
breaks := countBreaks(s.kids[1])
|
||||||
block(g, s.kids[1], true)
|
block(g, s.kids[1], true)
|
||||||
g.loops.pop()
|
g.loops.pop()
|
||||||
|
|
@ -1228,7 +1294,7 @@ fn forStmt(ptr[Gen] g, ast.Node s) {
|
||||||
c := op(g, "icmp slt i64 " + iv + ", " + to)
|
c := op(g, "icmp slt i64 " + iv + ", " + to)
|
||||||
condBr(g, c, body, end)
|
condBr(g, c, body, end)
|
||||||
label(g, body)
|
label(g, body)
|
||||||
g.loops.push(Loop(cont: step, brk: end))
|
g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len))
|
||||||
block(g, s.kids[2], true)
|
block(g, s.kids[2], true)
|
||||||
g.loops.pop()
|
g.loops.pop()
|
||||||
br(g, step)
|
br(g, step)
|
||||||
|
|
@ -1279,7 +1345,7 @@ fn eachStmt(ptr[Gen] g, ast.Node s) {
|
||||||
if copyElems {
|
if copyElems {
|
||||||
elem = copyValue(g, elem, et)
|
elem = copyValue(g, elem, et)
|
||||||
}
|
}
|
||||||
g.loops.push(Loop(cont: step, brk: end))
|
g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len))
|
||||||
pushScope(g)
|
pushScope(g)
|
||||||
addVar(g, s, s.name, et, elem, true)
|
addVar(g, s, s.name, et, elem, true)
|
||||||
block(g, s.kids[1], true)
|
block(g, s.kids[1], true)
|
||||||
|
|
@ -1295,10 +1361,14 @@ fn eachStmt(ptr[Gen] g, ast.Node s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returnStmt(ptr[Gen] g, ast.Node s) {
|
fn returnStmt(ptr[Gen] g, ast.Node s) {
|
||||||
|
if g.inDefer > 0 {
|
||||||
|
fail(g, s, "return can't be used inside defer")
|
||||||
|
}
|
||||||
if g.cur.ret.kind == "void" {
|
if g.cur.ret.kind == "void" {
|
||||||
if s.kids.len > 0 {
|
if s.kids.len > 0 {
|
||||||
fail(g, s.kids[0], "'" + g.cur.name + "' doesn't return a value (it has no '-> type')")
|
fail(g, s.kids[0], "'" + g.cur.name + "' doesn't return a value (it has no '-> type')")
|
||||||
}
|
}
|
||||||
|
unwind(g, 0)
|
||||||
emit(g, "ret void")
|
emit(g, "ret void")
|
||||||
g.term = true
|
g.term = true
|
||||||
return
|
return
|
||||||
|
|
@ -1317,6 +1387,7 @@ fn returnStmt(ptr[Gen] g, ast.Node s) {
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
out = own(g, v)
|
out = own(g, v)
|
||||||
}
|
}
|
||||||
|
unwind(g, 0)
|
||||||
emit(g, "ret " + llty(g, g.cur.ret) + " " + out)
|
emit(g, "ret " + llty(g, g.cur.ret) + " " + out)
|
||||||
g.term = true
|
g.term = true
|
||||||
}
|
}
|
||||||
|
|
@ -2433,7 +2504,13 @@ fn strMethod(ptr[Gen] g, ast.Node m, str s) -> Val {
|
||||||
emit(g, "call void @finch_str_repeat(ptr " + out + ", ptr " + s + ", i64 " + k + ")")
|
emit(g, "call void @finch_str_repeat(ptr " + out + ", ptr " + s + ", i64 " + k + ")")
|
||||||
return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true)
|
return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true)
|
||||||
}
|
}
|
||||||
fail(g, m, "a str has no method '" + n + "' (it has: sub, find, contains, starts_with, ends_with, split, trim, upper, lower, replace, repeat)")
|
if n == "bytes" {
|
||||||
|
need(g, m, 0)
|
||||||
|
ao := strOut(g)
|
||||||
|
emit(g, "call void @finch_bytes_from_str(ptr " + ao + ", ptr " + s + ")")
|
||||||
|
return Val(v: load(g, "{ ptr, i64, i64 }", ao), ty: ast.arrayOf(T("u8")), fresh: true)
|
||||||
|
}
|
||||||
|
fail(g, m, "a str has no method '" + n + "' (it has: sub, find, contains, starts_with, ends_with, split, trim, upper, lower, replace, repeat, bytes)")
|
||||||
return Val()
|
return Val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2476,6 +2553,7 @@ fn runtimeDecls() -> str {
|
||||||
"declare void @finch_str_split(ptr, ptr, ptr)",
|
"declare void @finch_str_split(ptr, ptr, ptr)",
|
||||||
"declare void @finch_str_join(ptr, ptr, ptr)",
|
"declare void @finch_str_join(ptr, ptr, ptr)",
|
||||||
"declare void @finch_str_repeat(ptr, ptr, i64)",
|
"declare void @finch_str_repeat(ptr, ptr, i64)",
|
||||||
|
"declare void @finch_bytes_from_str(ptr, ptr)",
|
||||||
"declare void @finch_arr_reserve(ptr, i64, i64)",
|
"declare void @finch_arr_reserve(ptr, i64, i64)",
|
||||||
"declare void @finch_arr_clone_raw(ptr, ptr, i64)",
|
"declare void @finch_arr_clone_raw(ptr, ptr, i64)",
|
||||||
"declare void @finch_arr_make(ptr, i64, i64)",
|
"declare void @finch_arr_make(ptr, i64, i64)",
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,16 @@ fn parseStatement(ptr[Parser] p) -> ast.Node {
|
||||||
if k == "{" {
|
if k == "{" {
|
||||||
return parseBlock(p)
|
return parseBlock(p)
|
||||||
}
|
}
|
||||||
if k == "fn" || k == "struct" || k == "import" || k == "defer" || k == "link" {
|
if k == "defer" {
|
||||||
|
s := node(p, "defer")
|
||||||
|
next(p)
|
||||||
|
if at(p, "defer") {
|
||||||
|
fail(p, cur(p), "defer inside defer is not allowed")
|
||||||
|
}
|
||||||
|
s.kids.push(parseStatement(p))
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if k == "fn" || k == "struct" || k == "import" || k == "link" {
|
||||||
fail(p, cur(p), "'" + k + "' can't be used here in the self-hosted compiler")
|
fail(p, cur(p), "'" + k + "' can't be used here in the self-hosted compiler")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@ fi
|
||||||
pass=0; failed=0; skipped=0
|
pass=0; failed=0; skipped=0
|
||||||
for f in tests/run/*.fch; do
|
for f in tests/run/*.fch; do
|
||||||
grep -q "^fn main" "$f" || continue
|
grep -q "^fn main" "$f" || continue
|
||||||
# the bootstrap compiler doesn't do C imports, defer, maps or errors as values
|
# the bootstrap compiler doesn't do C imports, maps or errors as values
|
||||||
# (checked in the test and in the modules it imports)
|
# (checked in the test and in the modules it imports)
|
||||||
files="$f"
|
files="$f"
|
||||||
for m in $(sed -n 's/^import \([a-z_]*\)$/\1/p' "$f"); do files="$files tests/run/$m.fch"; done
|
for m in $(sed -n 's/^import \([a-z_]*\)$/\1/p' "$f"); do files="$files tests/run/$m.fch"; done
|
||||||
if grep -qE '^import "|^link |defer | or |try |map\[|!\s*\{|for [a-z_]+, ' $files; then
|
if grep -qE '^import "|^link | or |try |map\[|!\s*\{|for [a-z_]+, ' $files; then
|
||||||
skipped=$((skipped + 1))
|
skipped=$((skipped + 1))
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue