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:
Nexoniarz 2026-10-25 18:53:39 +01:00
commit 083d66c82b
4 changed files with 460 additions and 16 deletions

View file

@ -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"

View file

@ -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)",

View file

@ -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
}

View file

@ -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