mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
boot: maps, for k, v in, for i, x in
map[K]V, [:] and ["a": 1] literals, m[k] reads and writes (m[k] += 1 starts from zero), has/get/remove/clear/keys/values, .len, copy and printing, and a loop over a map that stops with an error when it adds keys. for i, x in list gives the index too. boot.sh: 17 passed, 0 failed, 5 skipped (all five import C).
This commit is contained in:
parent
a5f417100d
commit
083d66c82b
4 changed files with 460 additions and 16 deletions
21
boot/ast.fch
21
boot/ast.fch
|
|
@ -21,6 +21,14 @@ fn arrayOf(Type t) -> Type {
|
|||
return out
|
||||
}
|
||||
|
||||
// map[K]V: elem holds the key, then the value
|
||||
fn mapOf(Type k, Type v) -> Type {
|
||||
out := Type(kind: "map")
|
||||
out.elem.push(k)
|
||||
out.elem.push(v)
|
||||
return out
|
||||
}
|
||||
|
||||
fn ptrTo(Type t) -> Type {
|
||||
out := Type(kind: "ptr")
|
||||
out.elem.push(t)
|
||||
|
|
@ -34,14 +42,16 @@ fn same(Type a, Type b) -> bool {
|
|||
if a.kind == "struct" || a.kind == "named" {
|
||||
return a.name == b.name && a.module == b.module
|
||||
}
|
||||
if a.kind == "array" || a.kind == "ptr" {
|
||||
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" {
|
||||
if a.elem.len != b.elem.len {
|
||||
return false
|
||||
}
|
||||
if a.elem.len == 0 {
|
||||
return true
|
||||
for i in 0..a.elem.len {
|
||||
if !same(a.elem[i], b.elem[i]) {
|
||||
return false
|
||||
}
|
||||
return same(a.elem[0], b.elem[0])
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -50,6 +60,9 @@ fn show(Type t) -> str {
|
|||
if t.kind == "array" {
|
||||
return "[]" + show(t.elem[0])
|
||||
}
|
||||
if t.kind == "map" {
|
||||
return "map[" + show(t.elem[0]) + "]" + show(t.elem[1])
|
||||
}
|
||||
if t.kind == "ptr" {
|
||||
if t.elem.len == 0 {
|
||||
return "ptr"
|
||||
|
|
|
|||
426
boot/gen.fch
426
boot/gen.fch
|
|
@ -280,6 +280,9 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
|||
if t.kind == "array" {
|
||||
return ast.arrayOf(resolve(g, t.elem[0], at))
|
||||
}
|
||||
if t.kind == "map" {
|
||||
return ast.mapOf(resolve(g, t.elem[0], at), resolve(g, t.elem[1], at))
|
||||
}
|
||||
if t.kind == "ptr" {
|
||||
if t.elem.len == 0 {
|
||||
return t
|
||||
|
|
@ -326,6 +329,9 @@ fn llty(ptr[Gen] g, ast.Type t) -> str {
|
|||
if k == "str" || k == "array" {
|
||||
return "{ ptr, i64, i64 }"
|
||||
}
|
||||
if k == "map" {
|
||||
return "{ ptr, i64, i64, i64, ptr, i64, i64 }" // entries, len, used, cap, index, icap, adds
|
||||
}
|
||||
if k == "ptr" || k == "null" {
|
||||
return "ptr"
|
||||
}
|
||||
|
|
@ -375,7 +381,7 @@ fn sizeOf(ptr[Gen] g, ast.Type t) -> str {
|
|||
}
|
||||
|
||||
fn owning(ptr[Gen] g, ast.Type t) -> bool {
|
||||
if t.kind == "str" || t.kind == "array" {
|
||||
if t.kind == "str" || t.kind == "array" || t.kind == "map" {
|
||||
return true
|
||||
}
|
||||
if t.kind == "struct" {
|
||||
|
|
@ -388,6 +394,9 @@ fn typeKey(ast.Type t) -> str {
|
|||
if t.kind == "array" {
|
||||
return "arr_" + typeKey(t.elem[0])
|
||||
}
|
||||
if t.kind == "map" {
|
||||
return "map_" + typeKey(t.elem[0]) + "_" + typeKey(t.elem[1])
|
||||
}
|
||||
if t.kind == "struct" {
|
||||
return "S_" + modName(t.module) + "_" + t.name
|
||||
}
|
||||
|
|
@ -506,7 +515,7 @@ fn loopEnd(ptr[Gen] g, []str l) {
|
|||
fn copyAt(ptr[Gen] g, str dst, str src, ast.Type t) {
|
||||
if t.kind == "str" {
|
||||
emit(g, "call void @finch_str_copy(ptr " + dst + ", ptr " + src + ")")
|
||||
} else if t.kind == "array" || (t.kind == "struct" && owning(g, t)) {
|
||||
} else if t.kind == "array" || t.kind == "map" || (t.kind == "struct" && owning(g, t)) {
|
||||
emit(g, "call void " + copyFn(g, t) + "(ptr " + dst + ", ptr " + src + ")")
|
||||
} else {
|
||||
store(g, llty(g, t), load(g, llty(g, t), src), dst)
|
||||
|
|
@ -533,6 +542,24 @@ fn copyFn(ptr[Gen] g, ast.Type t) -> str {
|
|||
copyAt(g, dp, spp, et)
|
||||
loopEnd(g, l)
|
||||
}
|
||||
} else if t.kind == "map" {
|
||||
emit(g, "call void @finch_map_clone_raw(ptr %dst, ptr %src, i64 " + entrySize(g, t) + ")")
|
||||
if owning(g, t.elem[0]) || owning(g, t.elem[1]) {
|
||||
// the slots were copied byte for byte: now give the copy its own keys and values
|
||||
used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr %src, i32 0, i32 2"))
|
||||
l := loopStart(g, used)
|
||||
from := entryAt(g, "%src", t, l[3])
|
||||
to := entryAt(g, "%dst", t, l[3])
|
||||
live := newLabel(g, "entry.live")
|
||||
next := newLabel(g, "entry.next")
|
||||
condBr(g, op(g, "icmp ne i64 " + load(g, "i64", op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + from + ", i32 0, i32 0")) + ", 0"), live, next)
|
||||
label(g, live)
|
||||
copyAt(g, entryField(g, to, t, 1), entryField(g, from, t, 1), t.elem[0])
|
||||
copyAt(g, entryField(g, to, t, 2), entryField(g, from, t, 2), t.elem[1])
|
||||
br(g, next)
|
||||
label(g, next)
|
||||
loopEnd(g, l)
|
||||
}
|
||||
} else {
|
||||
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||
st := llty(g, t)
|
||||
|
|
@ -696,6 +723,33 @@ fn formatFn(ptr[Gen] g, ast.Type t) -> str {
|
|||
emitFormat(g, "%buf", load(g, llty(g, et), ep), et, true)
|
||||
loopEnd(g, l)
|
||||
bufText(g, "%buf", "]")
|
||||
} else if t.kind == "map" {
|
||||
// {"a": 1, "b": 2}
|
||||
first := alloca(g, "i1")
|
||||
store(g, "i1", "true", first)
|
||||
bufText(g, "%buf", "{")
|
||||
used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr %a, i32 0, i32 2"))
|
||||
l := loopStart(g, used)
|
||||
e := entryAt(g, "%a", t, l[3])
|
||||
live := newLabel(g, "entry.live")
|
||||
next := newLabel(g, "entry.next")
|
||||
condBr(g, op(g, "icmp ne i64 " + load(g, "i64", entryField(g, e, t, 0)) + ", 0"), live, next)
|
||||
label(g, live)
|
||||
sep := newLabel(g, "sep")
|
||||
item := newLabel(g, "item")
|
||||
condBr(g, load(g, "i1", first), item, sep)
|
||||
label(g, sep)
|
||||
bufText(g, "%buf", ", ")
|
||||
br(g, item)
|
||||
label(g, item)
|
||||
store(g, "i1", "false", first)
|
||||
emitFormat(g, "%buf", load(g, llty(g, t.elem[0]), entryField(g, e, t, 1)), t.elem[0], true)
|
||||
bufText(g, "%buf", ": ")
|
||||
emitFormat(g, "%buf", load(g, llty(g, t.elem[1]), entryField(g, e, t, 2)), t.elem[1], true)
|
||||
br(g, next)
|
||||
label(g, next)
|
||||
loopEnd(g, l)
|
||||
bufText(g, "%buf", "}")
|
||||
} else {
|
||||
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||
st := llty(g, t)
|
||||
|
|
@ -1336,6 +1390,69 @@ fn forStmt(ptr[Gen] g, ast.Node s) {
|
|||
popScope(g)
|
||||
}
|
||||
|
||||
// for key in map / for key, value in map: in the order the keys were added
|
||||
fn eachMap(ptr[Gen] g, ast.Node s, str mp, ast.Type t, bool live) {
|
||||
kt := t.elem[0]
|
||||
vt := t.elem[1]
|
||||
changes := live && mutates(g, rootVar(s.kids[0]), s.kids[1])
|
||||
iv := alloca(g, "i64")
|
||||
store(g, "i64", "0", iv)
|
||||
adds := ""
|
||||
if live {
|
||||
adds = load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 6"))
|
||||
}
|
||||
cond := newLabel(g, "map.cond")
|
||||
check := newLabel(g, "map.check")
|
||||
body := newLabel(g, "map.body")
|
||||
step := newLabel(g, "map.step")
|
||||
end := newLabel(g, "map.end")
|
||||
br(g, cond)
|
||||
label(g, cond)
|
||||
// adding keys inside the loop could reorder the entries: that stops the program (removing is fine)
|
||||
if live && changes {
|
||||
changed := newLabel(g, "map.changed")
|
||||
same := newLabel(g, "map.same")
|
||||
now := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 6"))
|
||||
condBr(g, op(g, "icmp ne i64 " + now + ", " + adds), changed, same)
|
||||
label(g, changed)
|
||||
emit(g, "call void @finch_map_changed(ptr " + strGlobal(g, g.files[s.file]) + ", i64 " + str(s.line) + ")")
|
||||
emit(g, "unreachable")
|
||||
g.term = true
|
||||
label(g, same)
|
||||
}
|
||||
i := load(g, "i64", iv)
|
||||
used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 2"))
|
||||
condBr(g, op(g, "icmp slt i64 " + i + ", " + used), check, end)
|
||||
label(g, check)
|
||||
entry := entryAt(g, mp, t, i)
|
||||
hash := load(g, "i64", entryField(g, entry, t, 0))
|
||||
condBr(g, op(g, "icmp eq i64 " + hash + ", 0"), step, body) // a removed entry
|
||||
label(g, body)
|
||||
g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len))
|
||||
pushScope(g)
|
||||
kv := load(g, llty(g, kt), entryField(g, entry, t, 1))
|
||||
if changes {
|
||||
kv = copyValue(g, kv, kt)
|
||||
}
|
||||
addVar(g, s, s.name, kt, kv, true)
|
||||
if s.text.len > 0 {
|
||||
vv := load(g, llty(g, vt), entryField(g, entry, t, 2))
|
||||
if changes {
|
||||
vv = copyValue(g, vv, vt)
|
||||
}
|
||||
addVar(g, s, s.text, vt, vv, true)
|
||||
}
|
||||
block(g, s.kids[1], true)
|
||||
popScope(g)
|
||||
g.loops.pop()
|
||||
br(g, step)
|
||||
label(g, step)
|
||||
i2 := load(g, "i64", iv)
|
||||
store(g, "i64", op(g, "add i64 " + i2 + ", 1"), iv)
|
||||
br(g, cond)
|
||||
label(g, end)
|
||||
}
|
||||
|
||||
fn eachStmt(ptr[Gen] g, ast.Node s) {
|
||||
pushScope(g)
|
||||
r := ref(g, s.kids[0], false)
|
||||
|
|
@ -1348,8 +1465,13 @@ fn eachStmt(ptr[Gen] g, ast.Node s) {
|
|||
lt = r.val.ty
|
||||
listAddr = spill(g, r.val)
|
||||
}
|
||||
if lt.kind == "map" {
|
||||
eachMap(g, s, listAddr, lt, r.isPlace)
|
||||
popScope(g)
|
||||
return
|
||||
}
|
||||
if lt.kind != "array" && lt.kind != "str" {
|
||||
fail(g, s.kids[0], "for ... in needs an array or a str, but this is " + ast.show(lt) + " (for a range of numbers write 0..n)")
|
||||
fail(g, s.kids[0], "for ... in needs an array, a str or a map, but this is " + ast.show(lt) + " (for a range of numbers write 0..n)")
|
||||
}
|
||||
et := T("char")
|
||||
if lt.kind == "array" {
|
||||
|
|
@ -1377,7 +1499,12 @@ fn eachStmt(ptr[Gen] g, ast.Node s) {
|
|||
}
|
||||
g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len))
|
||||
pushScope(g)
|
||||
addVar(g, s, s.name, et, elem, true)
|
||||
elemVar := s.name
|
||||
if s.text.len > 0 { // for i, x in list
|
||||
addVar(g, s, s.name, T("int"), i, true)
|
||||
elemVar = s.text
|
||||
}
|
||||
addVar(g, s, elemVar, et, elem, true)
|
||||
block(g, s.kids[1], true)
|
||||
popScope(g)
|
||||
g.loops.pop()
|
||||
|
|
@ -1452,6 +1579,259 @@ fn returnStmt(ptr[Gen] g, ast.Node s) {
|
|||
|
||||
// ---------- expressions ----------
|
||||
|
||||
// ---------- maps ----------
|
||||
//
|
||||
// map[K]V is { entries, len, used, cap, index, icap, adds } (see runtime/finch_rt.c). The runtime finds and
|
||||
// places entries; the compiler stores, copies and drops the keys and values in them. An entry is
|
||||
// { i64 hash, K key, V value }; hash 0 marks a removed entry.
|
||||
|
||||
fn entryTy(ptr[Gen] g, ast.Type t) -> str {
|
||||
return "{ i64, " + llty(g, t.elem[0]) + ", " + llty(g, t.elem[1]) + " }"
|
||||
}
|
||||
|
||||
fn entrySize(ptr[Gen] g, ast.Type t) -> str {
|
||||
return "ptrtoint (ptr getelementptr (" + entryTy(g, t) + ", ptr null, i32 1) to i64)"
|
||||
}
|
||||
|
||||
// the address of entry number `i` of the map at `mp`
|
||||
fn entryAt(ptr[Gen] g, str mp, ast.Type t, str i) -> str {
|
||||
entries := load(g, "ptr", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 0"))
|
||||
return op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + entries + ", i64 " + i)
|
||||
}
|
||||
|
||||
// field 0 hash, 1 key, 2 value
|
||||
fn entryField(ptr[Gen] g, str entry, ast.Type t, int f) -> str {
|
||||
return op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + entry + ", i32 0, i32 " + str(f))
|
||||
}
|
||||
|
||||
fn mapArgs(ptr[Gen] g, str mp, str keyAddr, ast.Type t) -> str {
|
||||
kstr := "0"
|
||||
if t.elem[0].kind == "str" {
|
||||
kstr = "1"
|
||||
}
|
||||
return "ptr " + mp + ", ptr " + keyAddr + ", i64 " + entrySize(g, t) + ", i64 " + sizeOf(g, t.elem[0]) + ", i32 " + kstr
|
||||
}
|
||||
|
||||
fn mapFind(ptr[Gen] g, str mp, str keyAddr, ast.Type t) -> str {
|
||||
return op(g, "call i64 @finch_map_find(" + mapArgs(g, mp, keyAddr, t) + ")")
|
||||
}
|
||||
|
||||
fn defaultVal(ptr[Gen] g, ast.Type t, ast.Node at) -> str {
|
||||
if t.kind == "struct" && hasDefaults(g, t) {
|
||||
[]ast.Node noArgs
|
||||
[]str noNames
|
||||
return construct(g, findStruct(g, t.module, t.name), noArgs, noNames, at).v
|
||||
}
|
||||
return zeroOf(t)
|
||||
}
|
||||
|
||||
// The entry for `key`, added if it is missing: the key is copied in, and the value is its
|
||||
// type's default when `fill` (m[k] += 1 starts from 0).
|
||||
fn mapSlot(ptr[Gen] g, str mp, ast.Type t, Val key, bool fill, ast.Node at) -> str {
|
||||
keyAddr := spill(g, key)
|
||||
added := alloca(g, "i32")
|
||||
store(g, "i32", "0", added)
|
||||
i := op(g, "call i64 @finch_map_slot(" + mapArgs(g, mp, keyAddr, t) + ", ptr " + added + ")")
|
||||
entry := entryAt(g, mp, t, i)
|
||||
fresh := newLabel(g, "map.new")
|
||||
have := newLabel(g, "map.have")
|
||||
condBr(g, op(g, "icmp ne i32 " + load(g, "i32", added) + ", 0"), fresh, have)
|
||||
label(g, fresh)
|
||||
copyAt(g, entryField(g, entry, t, 1), keyAddr, t.elem[0])
|
||||
if fill {
|
||||
store(g, llty(g, t.elem[1]), own(g, Val(v: defaultVal(g, t.elem[1], at), ty: t.elem[1], fresh: true)), entryField(g, entry, t, 2))
|
||||
}
|
||||
br(g, have)
|
||||
label(g, have)
|
||||
return entry
|
||||
}
|
||||
|
||||
// m[key]: reading a missing key stops the program; writing (m[k] = v, m[k] += 1, m[k].push(x)) adds it
|
||||
fn mapIndex(ptr[Gen] g, ast.Node e, Ref obj, ast.Type t, bool write) -> Ref {
|
||||
kt := t.elem[0]
|
||||
vt := t.elem[1]
|
||||
key := coerce(g, exprWant(g, e.kids[1], kt), kt, e.kids[1], "the key")
|
||||
if write {
|
||||
if !obj.isPlace {
|
||||
fail(g, e, "this is a temporary map, it can't be changed")
|
||||
}
|
||||
entry := mapSlot(g, obj.pl.addr, t, key, true, e)
|
||||
return Ref(isPlace: true, pl: Place(addr: entryField(g, entry, t, 2), ty: vt))
|
||||
}
|
||||
mp := ""
|
||||
if obj.isPlace {
|
||||
mp = obj.pl.addr
|
||||
} else {
|
||||
mp = spill(g, obj.val)
|
||||
}
|
||||
keyAddr := spill(g, key)
|
||||
i := mapFind(g, mp, keyAddr, t)
|
||||
missing := newLabel(g, "map.missing")
|
||||
found := newLabel(g, "map.found")
|
||||
condBr(g, op(g, "icmp slt i64 " + i + ", 0"), missing, found)
|
||||
label(g, missing)
|
||||
text := alloca(g, "{ ptr, i64, i64 }")
|
||||
store(g, "{ ptr, i64, i64 }", "zeroinitializer", text)
|
||||
emitFormat(g, text, key.v, kt, true)
|
||||
emit(g, "call void @finch_map_missing(ptr " + strGlobal(g, g.files[e.file]) + ", i64 " + str(e.line) + ", ptr " + text + ")")
|
||||
emit(g, "unreachable")
|
||||
g.term = true
|
||||
label(g, found)
|
||||
va := entryField(g, entryAt(g, mp, t, i), t, 2)
|
||||
if obj.isPlace {
|
||||
return Ref(isPlace: true, pl: Place(addr: va, ty: vt))
|
||||
}
|
||||
v := load(g, llty(g, vt), va)
|
||||
out := Val(v: v, ty: vt)
|
||||
if owning(g, vt) && obj.val.fresh {
|
||||
out = Val(v: copyValue(g, v, vt), ty: vt, fresh: true)
|
||||
}
|
||||
return Ref(isPlace: false, val: out)
|
||||
}
|
||||
|
||||
// ["a": 1, "b": 2], or [:] where the type is known
|
||||
fn mapLit(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
||||
t := want
|
||||
first := false
|
||||
k0 := Val()
|
||||
v0 := Val()
|
||||
if want.kind != "map" {
|
||||
if e.kids.len == 0 {
|
||||
fail(g, e, "an empty [:] needs a type, like: map[str]int m = [:]")
|
||||
}
|
||||
// number values written in the code are all floats if any of them has a dot
|
||||
allNum := true
|
||||
anyFloat := false
|
||||
for i in 0..e.kids.len / 2 {
|
||||
y := e.kids[2 * i + 1]
|
||||
if y.kind == "unary" && y.name == "-" {
|
||||
y = y.kids[0]
|
||||
}
|
||||
if y.kind == "float" {
|
||||
anyFloat = true
|
||||
} else if y.kind != "int" {
|
||||
allNum = false
|
||||
}
|
||||
}
|
||||
k0 = expr(g, e.kids[0])
|
||||
v0 = expr(g, e.kids[1])
|
||||
if k0.ty.kind == "void" || k0.ty.kind == "null" {
|
||||
fail(g, e.kids[0], "a map key needs a value with a type")
|
||||
}
|
||||
if v0.ty.kind == "void" || v0.ty.kind == "null" {
|
||||
fail(g, e.kids[1], "a map value needs a value with a type")
|
||||
}
|
||||
vt := v0.ty
|
||||
if allNum && anyFloat {
|
||||
vt = T("float")
|
||||
}
|
||||
t = ast.mapOf(k0.ty, vt)
|
||||
first = true
|
||||
}
|
||||
mp := alloca(g, llty(g, t))
|
||||
store(g, llty(g, t), "zeroinitializer", mp)
|
||||
for i in 0..e.kids.len / 2 {
|
||||
k := k0
|
||||
v := v0
|
||||
if !(i == 0 && first) {
|
||||
k = exprWant(g, e.kids[2 * i], t.elem[0])
|
||||
v = exprWant(g, e.kids[2 * i + 1], t.elem[1])
|
||||
}
|
||||
k = coerce(g, k, t.elem[0], e.kids[2 * i], "every key of this map")
|
||||
v = coerce(g, v, t.elem[1], e.kids[2 * i + 1], "every value of this map")
|
||||
vv := own(g, v)
|
||||
entry := mapSlot(g, mp, t, k, false, e)
|
||||
store(g, llty(g, t.elem[1]), vv, entryField(g, entry, t, 2))
|
||||
}
|
||||
return Val(v: load(g, llty(g, t), mp), ty: t, fresh: true)
|
||||
}
|
||||
|
||||
fn mapMethod(ptr[Gen] g, ast.Node m, str mp, ast.Type t) -> Val {
|
||||
n := m.name
|
||||
kt := t.elem[0]
|
||||
vt := t.elem[1]
|
||||
if n == "has" || n == "get" || n == "remove" {
|
||||
if n == "get" {
|
||||
checkArgs(g, m, 1, 2, n)
|
||||
} else {
|
||||
checkArgs(g, m, 1, 1, n)
|
||||
}
|
||||
key := coerce(g, exprWant(g, m.kids[1], kt), kt, m.kids[1], "the key")
|
||||
i := mapFind(g, mp, spill(g, key), t)
|
||||
was := op(g, "icmp sge i64 " + i + ", 0")
|
||||
if n == "has" {
|
||||
return Val(v: was, ty: T("bool"))
|
||||
}
|
||||
if n == "remove" {
|
||||
hit := newLabel(g, "remove.hit")
|
||||
end := newLabel(g, "remove.end")
|
||||
condBr(g, was, hit, end)
|
||||
label(g, hit)
|
||||
emit(g, "call void @finch_map_remove_at(ptr " + mp + ", i64 " + i + ", i64 " + entrySize(g, t) + ")")
|
||||
br(g, end)
|
||||
label(g, end)
|
||||
return Val(v: was, ty: T("bool"))
|
||||
}
|
||||
// get(key, default): the default is only computed when the key is missing
|
||||
found := newLabel(g, "get.found")
|
||||
missing := newLabel(g, "get.missing")
|
||||
fend := newLabel(g, "get.fend")
|
||||
mend := newLabel(g, "get.mend")
|
||||
end := newLabel(g, "get.end")
|
||||
condBr(g, was, found, missing)
|
||||
label(g, found)
|
||||
v := copyValue(g, load(g, llty(g, vt), entryField(g, entryAt(g, mp, t, i), t, 2)), vt)
|
||||
br(g, fend)
|
||||
label(g, fend)
|
||||
br(g, end)
|
||||
label(g, missing)
|
||||
d := own(g, coerce(g, exprWant(g, m.kids[2], vt), vt, m.kids[2], "the default"))
|
||||
br(g, mend)
|
||||
label(g, mend)
|
||||
br(g, end)
|
||||
label(g, end)
|
||||
return Val(v: op(g, "phi " + llty(g, vt) + " [ " + v + ", %" + fend + " ], [ " + d + ", %" + mend + " ]"), ty: vt, fresh: owning(g, vt))
|
||||
}
|
||||
if n == "clear" {
|
||||
checkArgs(g, m, 1, 0, n)
|
||||
emit(g, "call void @finch_map_clear(ptr " + mp + ")")
|
||||
return Val(ty: T("void"))
|
||||
}
|
||||
if n == "keys" || n == "values" {
|
||||
checkArgs(g, m, 1, 0, n)
|
||||
field := 1
|
||||
et := kt
|
||||
if n == "values" {
|
||||
field = 2
|
||||
et = vt
|
||||
}
|
||||
at := ast.arrayOf(et)
|
||||
out := alloca(g, "{ ptr, i64, i64 }")
|
||||
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||
len := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 1"))
|
||||
emit(g, "call void @finch_arr_make(ptr " + out + ", i64 " + len + ", i64 " + sizeOf(g, et) + ")")
|
||||
data := load(g, "ptr", out)
|
||||
j := alloca(g, "i64")
|
||||
store(g, "i64", "0", j)
|
||||
used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 2"))
|
||||
l := loopStart(g, used)
|
||||
entry := entryAt(g, mp, t, l[3])
|
||||
live := newLabel(g, "entry.live")
|
||||
next := newLabel(g, "entry.next")
|
||||
condBr(g, op(g, "icmp ne i64 " + load(g, "i64", entryField(g, entry, t, 0)) + ", 0"), live, next)
|
||||
label(g, live)
|
||||
jv := load(g, "i64", j)
|
||||
copyAt(g, op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + data + ", i64 " + jv), entryField(g, entry, t, field), et)
|
||||
store(g, "i64", op(g, "add i64 " + jv + ", 1"), j)
|
||||
br(g, next)
|
||||
label(g, next)
|
||||
loopEnd(g, l)
|
||||
return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: at, fresh: true)
|
||||
}
|
||||
fail(g, m, "a map has no method '" + n + "' (it has: has, get, remove, clear, keys, values)")
|
||||
return Val()
|
||||
}
|
||||
|
||||
// ---------- errors as values ----------
|
||||
|
||||
struct Fallible {
|
||||
|
|
@ -1558,6 +1938,9 @@ fn exprWant(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
|||
if e.kind == "array" {
|
||||
return arrayLit(g, e, want)
|
||||
}
|
||||
if e.kind == "map" {
|
||||
return mapLit(g, e, want)
|
||||
}
|
||||
return expr(g, e)
|
||||
}
|
||||
|
||||
|
|
@ -1624,6 +2007,9 @@ fn expr(ptr[Gen] g, ast.Node e) -> Val {
|
|||
if k == "array" {
|
||||
return arrayLit(g, e, T("void"))
|
||||
}
|
||||
if k == "map" {
|
||||
return mapLit(g, e, T("void"))
|
||||
}
|
||||
fail(g, e, "unknown expression " + k)
|
||||
return Val()
|
||||
}
|
||||
|
|
@ -1947,18 +2333,18 @@ fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
|||
obj = Ref(isPlace: true, pl: Place(addr: pv, ty: target))
|
||||
t = target
|
||||
}
|
||||
if t.kind == "array" || t.kind == "str" {
|
||||
if t.kind == "array" || t.kind == "str" || t.kind == "map" {
|
||||
if m.name != "len" {
|
||||
fail(g, m, "an array or str has .len, not '." + m.name + "'")
|
||||
fail(g, m, "an array, map or str has .len, not '." + m.name + "'")
|
||||
}
|
||||
if write {
|
||||
fail(g, m, ".len can't be changed directly (use push or pop)")
|
||||
}
|
||||
if obj.isPlace {
|
||||
lp := op(g, "getelementptr inbounds { ptr, i64, i64 }, ptr " + obj.pl.addr + ", i32 0, i32 1")
|
||||
lp := op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + obj.pl.addr + ", i32 0, i32 1")
|
||||
return Ref(isPlace: false, val: Val(v: load(g, "i64", lp), ty: T("int")))
|
||||
}
|
||||
return Ref(isPlace: false, val: Val(v: op(g, "extractvalue { ptr, i64, i64 } " + obj.val.v + ", 1"), ty: T("int")))
|
||||
return Ref(isPlace: false, val: Val(v: op(g, "extractvalue " + llty(g, t) + " " + obj.val.v + ", 1"), ty: T("int")))
|
||||
}
|
||||
if t.kind != "struct" {
|
||||
fail(g, m, ast.show(t) + " has no fields (no '." + m.name + "')")
|
||||
|
|
@ -1987,6 +2373,9 @@ fn index(ptr[Gen] g, ast.Node e, bool write) -> Ref {
|
|||
if obj.isPlace {
|
||||
t = obj.pl.ty
|
||||
}
|
||||
if t.kind == "map" {
|
||||
return mapIndex(g, e, obj, t, write)
|
||||
}
|
||||
i := coerce(g, expr(g, e.kids[1]), T("int"), e.kids[1], "an index").v
|
||||
if t.kind == "ptr" {
|
||||
if t.elem.len == 0 {
|
||||
|
|
@ -2182,7 +2571,7 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
} else if k == "bool" {
|
||||
z := op(g, "zext i1 " + v.v + " to i32")
|
||||
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
|
||||
} else if k == "bool" || k == "char" || k == "array" || k == "struct" {
|
||||
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" {
|
||||
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||
emitFormat(g, out, v.v, v.ty, false)
|
||||
} else {
|
||||
|
|
@ -2445,6 +2834,18 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
|||
}
|
||||
return strMethod(g, m, spill(g, sv))
|
||||
}
|
||||
if t.kind == "map" {
|
||||
mp := ""
|
||||
if r.isPlace {
|
||||
mp = r.pl.addr
|
||||
} else {
|
||||
if changing.contains(m.name) {
|
||||
fail(g, m, m.name + "() would change a temporary map; store it in a variable first")
|
||||
}
|
||||
mp = spill(g, r.val)
|
||||
}
|
||||
return mapMethod(g, m, mp, t)
|
||||
}
|
||||
st := t
|
||||
selfAddr := ""
|
||||
if t.kind == "ptr" && t.elem.len > 0 && t.elem[0].kind == "struct" {
|
||||
|
|
@ -2780,6 +3181,13 @@ fn runtimeDecls() -> str {
|
|||
"declare void @finch_str_join(ptr, ptr, ptr)",
|
||||
"declare void @finch_str_repeat(ptr, ptr, i64)",
|
||||
"declare void @finch_bytes_from_str(ptr, ptr)",
|
||||
"declare i64 @finch_map_find(ptr, ptr, i64, i64, i32)",
|
||||
"declare i64 @finch_map_slot(ptr, ptr, i64, i64, i32, ptr)",
|
||||
"declare void @finch_map_remove_at(ptr, i64, i64)",
|
||||
"declare void @finch_map_clear(ptr)",
|
||||
"declare void @finch_map_clone_raw(ptr, ptr, i64)",
|
||||
"declare void @finch_map_missing(ptr, i64, ptr) noreturn cold",
|
||||
"declare void @finch_map_changed(ptr, i64) noreturn cold",
|
||||
"declare void @finch_arr_reserve(ptr, i64, i64)",
|
||||
"declare void @finch_arr_clone_raw(ptr, ptr, i64)",
|
||||
"declare void @finch_arr_make(ptr, i64, i64)",
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ fn parseType(ptr[Parser] p) -> ast.Type {
|
|||
fail(p, cur(p), "unexpected " + describe(cur(p)) + " (expected a type like int, str, []int or a struct name)")
|
||||
}
|
||||
name := next(p).text
|
||||
if name == "map" && at(p, "[") {
|
||||
next(p)
|
||||
k := parseType(p)
|
||||
expect(p, "]", "']' (a map type looks like map[str]int)")
|
||||
return ast.mapOf(k, parseType(p))
|
||||
}
|
||||
if isTypeName(name) {
|
||||
if name == "ptr" && accept(p, "[") {
|
||||
t := ast.ptrTo(parseType(p))
|
||||
|
|
@ -133,7 +139,7 @@ fn atDeclaration(ptr[Parser] p) -> bool {
|
|||
if n == "ident" {
|
||||
return !p.toks[p.i + 1].nl
|
||||
}
|
||||
if cur(p).text == "ptr" && n == "[" {
|
||||
if (cur(p).text == "ptr" || cur(p).text == "map") && n == "[" {
|
||||
return true
|
||||
}
|
||||
return n == "." && peekKind(p, 2) == "ident" && peekKind(p, 3) == "ident" && !p.toks[p.i + 3].nl
|
||||
|
|
@ -263,9 +269,15 @@ fn parseStatement(ptr[Parser] p) -> ast.Node {
|
|||
s := node(p, "for")
|
||||
next(p)
|
||||
s.name = expect(p, "ident", "a loop variable name").text
|
||||
if accept(p, ",") {
|
||||
s.text = expect(p, "ident", "a second loop variable name").text
|
||||
}
|
||||
expect(p, "in", "'in'")
|
||||
s.kids.push(parseExpr(p))
|
||||
if accept(p, "..") {
|
||||
if s.text.len > 0 {
|
||||
fail(p, cur(p), "a range of numbers has one loop variable: for i in 0..n")
|
||||
}
|
||||
s.kids.push(parseExpr(p))
|
||||
} else {
|
||||
s.kind = "each"
|
||||
|
|
@ -516,8 +528,19 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
|
|||
next(p)
|
||||
e.kind = "array"
|
||||
p.depth += 1
|
||||
if at(p, ":") && peekKind(p, 1) == "]" { // [:], an empty map
|
||||
next(p)
|
||||
e.kind = "map"
|
||||
}
|
||||
while !at(p, "]") {
|
||||
e.kids.push(parseExpr(p))
|
||||
if e.kids.len == 1 && at(p, ":") { // ["a": 1, ...]
|
||||
e.kind = "map"
|
||||
}
|
||||
if e.kind == "map" {
|
||||
expect(p, ":", "':' (a map entry looks like \"key\": value)")
|
||||
e.kids.push(parseExpr(p))
|
||||
}
|
||||
if !accept(p, ",") {
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ fi
|
|||
pass=0; failed=0; skipped=0
|
||||
for f in tests/run/*.fch; do
|
||||
grep -q "^fn main" "$f" || continue
|
||||
# the bootstrap compiler doesn't do C imports and maps
|
||||
# the bootstrap compiler doesn't do C imports
|
||||
# (checked in the test and in the modules it imports)
|
||||
files="$f"
|
||||
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 |map\[|for [a-z_]+, ' $files; then
|
||||
if grep -qE '^import "|^link ' $files; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue