Skip to content

Commit f738ffd

Browse files
authored
c: basic map implementation (#337)
1 parent 4e9fa3e commit f738ffd

12 files changed

Lines changed: 270 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,17 @@ jobs:
5151
bait test -b c tests/array
5252
bait test -b c tests/auto_str
5353
bait test -b c tests/for/loop_test.bt
54+
bait test -b c tests/for/for_in_test.bt
5455
bait test -b c tests/int/promotion_test.bt
55-
bait test -b c tests/other/bitwise_test.bt
56+
# bait test -b c tests/int/bitwise_test.bt
5657
- name: Run lib tests
5758
run: |
5859
# bait test -b c lib/bait
5960
bait test -b c lib/builtin
6061
# bait test -b c lib/cli
6162
bait test -b c lib/encoding
6263
bait test -b c lib/hash
63-
# bait test -b c lib/os
64+
bait test -b c lib/os
6465
bait test -b c lib/strings
6566
# - name: Run tool tests
6667
# run: bait test cli/tools

lib/bait/gen/c/cgen.bt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ fun (mut g Gen) save_stmt_offset() {
173173
}
174174

175175
fun c_name(n string) string {
176-
return n.replace('.', '__').replace('[]', 'Array_')
176+
return n.replace('.', '__').replace('[]', 'Array_').replace('[', '_').replace(']', '_')
177177
}
178178

179179
fun c_esc(n string) string {

lib/bait/gen/c/expr.bt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fun (mut g Gen) expr(expr ast.Expr) {
2424
ast.IndexExpr { g.index_expr(expr) }
2525
ast.InfixExpr { g.infix_expr(expr) }
2626
ast.IntegerLiteral { g.integer_literal(expr) }
27-
ast.MapInit { panic('maps not implemented') } // TODO
27+
ast.MapInit { g.map_init(expr) }
2828
ast.ParExpr { g.par_expr(expr) }
2929
ast.PrefixExpr { g.prefix_expr(expr)}
3030
ast.RangeExpr { panic('ranges not implemented') } // TODO
@@ -82,6 +82,31 @@ fun (mut g Gen) array_init(node ast.ArrayInit){
8282
g.write('})')
8383
}
8484

85+
fun (mut g Gen) map_init(node ast.MapInit) {
86+
val_type_str := g.typ(node.val_type)
87+
if node.keys.length == 0 {
88+
g.write('new_map(sizeof(${val_type_str}), 0)')
89+
return
90+
}
91+
92+
len := node.keys.length
93+
g.write('new_map_from_c(${len}, sizeof(${val_type_str}), (string[${len}]){')
94+
for i, key in node.keys {
95+
g.expr_to_string(key, node.key_type)
96+
if i < len - 1 {
97+
g.write(', ')
98+
}
99+
}
100+
g.write('}, (${val_type_str}[${len}]){')
101+
for i, val in node.vals {
102+
g.expr(val)
103+
if i < len - 1 {
104+
g.write(', ')
105+
}
106+
}
107+
g.write('})')
108+
}
109+
85110
fun (mut g Gen) as_cast(node ast.AsCast) {
86111
type_str := g.typ(node.target)
87112
g.write('(${type_str})(')
@@ -126,7 +151,7 @@ fun (mut g Gen) index_expr(node ast.IndexExpr) {
126151
val_type_str := g.typ(info.val_type)
127152
if g.is_lhs_assign and not node.is_selector {
128153
g.is_array_map_set = true
129-
g.write('map_set((map*)&')
154+
g.write('map_set((Map*)&')
130155
g.expr(node.left)
131156
g.write(', ')
132157
g.expr(node.index)

lib/bait/gen/c/stmt.bt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,26 @@ fun (mut g Gen) for_classic_loop(node ast.ForClassicLoop) {
145145

146146
fun (mut g Gen) for_in_loop(node ast.ForInLoop){
147147
sym := g.table.get_sym(node.expr_type)
148+
if sym.kind == .map {
149+
g.for_in_map(node)
150+
return
151+
}
152+
148153
i := g.expr_string(node.idxvar)
149154
container := g.expr_string(node.expr)
150155
g.writeln('for (i32 ${i} = 0; ${i} < ${container}.length; ${i}++) {')
151-
// TODO for in map
156+
g.stmts(node.stmts)
157+
g.writeln('}')
158+
}
159+
160+
fun (mut g Gen) for_in_map(node ast.ForInLoop){
161+
container := g.expr_string(node.expr)
162+
keys_var := g.new_temp_var()
163+
g.writeln('Array ${keys_var} = Map_keys(${container});')
164+
i := g.new_temp_var()
165+
idxvar := g.expr_string(node.idxvar)
166+
g.writeln('for (i32 ${i} = 0; ${i} < ${keys_var}.length; ${i}++) {')
167+
g.writeln('\tstring ${idxvar} = *(string*)(Array_get(${keys_var}, ${i}));')
152168
g.stmts(node.stmts)
153169
g.writeln('}')
154170
}

lib/bait/gen/c/type.bt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fun (g Gen) concrete_sym(typ ast.Type) ast.TypeSymbol {
1616
fun (g Gen) typ(typ ast.Type) string {
1717
sym := g.concrete_sym(typ)
1818

19-
name := sym.mix_name.replace('[]', 'Array_').replace('C.', '').replace('.', '__')
19+
name := c_name(sym.mix_name.replace('C.', ''))
2020
ptrs := '*'.repeat(typ.get_nr_amp())
2121
prefix := if sym.kind == .enum_ { 'enum ' } else { '' }
2222
return prefix + name + ptrs
@@ -41,6 +41,8 @@ fun (mut g Gen) write_types() {
4141
g.type_impls_out += '};\n'
4242
} else if sym.info is ast.ArrayInfo {
4343
g.type_defs_out += 'typedef Array ${cname};\n'
44+
} else if sym.info is ast.MapInfo {
45+
g.type_defs_out += 'typedef Map ${cname};\n'
4446
} else if sym.kind == .fun_ {
4547
info := sym.info as ast.FunInfo
4648
if not info.is_alias {

lib/builtin/map.c.bt

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// SPDX-FileCopyrightText: Lukas Neubert
2+
// SPDX-License-Identifier: MIT
3+
package builtin
4+
5+
import hash.fnv1a
6+
7+
const map_min_cap := 8
8+
const map_max_load_num := 7
9+
const map_max_load_den := 10
10+
11+
struct Map {
12+
mut hashes []u32
13+
mut used []bool
14+
mut keys []string
15+
mut order []string
16+
mut vals Array
17+
pub mut length i32
18+
mut cap i32
19+
mut val_size i32
20+
mut zero &void
21+
}
22+
23+
fun map_hash(key string) u32 {
24+
hash := fnv1a.sum32_string(key)
25+
if hash == 0 {
26+
return 1
27+
}
28+
return hash
29+
}
30+
31+
fun map_key_eq(a string, b string) bool {
32+
if a.length != b.length {
33+
return false
34+
}
35+
for i := 0; i < a.length; i += 1 {
36+
if a[i] != b[i] {
37+
return false
38+
}
39+
}
40+
return true
41+
}
42+
43+
fun map_next_cap(min_cap i32) i32 {
44+
mut cap := map_min_cap
45+
for cap < min_cap {
46+
cap *= 2
47+
}
48+
return cap
49+
}
50+
51+
pub fun new_map(val_size i32, min_cap i32) Map {
52+
cap := map_next_cap(min_cap)
53+
return Map{
54+
hashes = []u32{length = cap}
55+
used = []bool{length = cap}
56+
keys = []string{length = cap}
57+
order = []string{cap = cap}
58+
vals = new_array(cap, cap, val_size)
59+
cap = cap
60+
val_size = val_size
61+
zero = #C.calloc(1, val_size) as &void
62+
}
63+
}
64+
65+
pub fun new_map_from_c(len i32, val_size i32, keys &string, vals &void) Map {
66+
mut m := new_map(val_size, len * 2)
67+
for i := 0; i < len; i += 1 {
68+
key := *(#C.'keys + i' as &string)
69+
val := #C.'vals + i * val_size' as &void
70+
map_set(&m, key, val)
71+
}
72+
return m
73+
}
74+
75+
fun map_rehash(mut m &Map, new_cap i32) {
76+
old_hashes := m.hashes
77+
old_used := m.used
78+
old_keys := m.keys
79+
old_order := m.order
80+
old_vals := m.vals
81+
old_cap := m.cap
82+
83+
m.hashes = []u32{length = new_cap}
84+
m.used = []bool{length = new_cap}
85+
m.keys = []string{length = new_cap}
86+
m.order = []string{cap = old_order.length}
87+
m.vals = new_array(new_cap, new_cap, m.val_size)
88+
m.cap = new_cap
89+
m.length = 0
90+
91+
old_map := Map{
92+
hashes = old_hashes
93+
used = old_used
94+
keys = old_keys
95+
order = old_order
96+
vals = old_vals
97+
length = old_order.length
98+
cap = old_cap
99+
val_size = m.val_size
100+
zero = m.zero
101+
}
102+
103+
for key in old_order {
104+
map_set(mut m, key, map_get(&old_map, key))
105+
}
106+
}
107+
108+
fun map_ensure_cap(mut m &Map, min_len i32) {
109+
if m.cap == 0 {
110+
map_rehash(mut m, map_min_cap)
111+
return
112+
}
113+
114+
if min_len * map_max_load_den < m.cap * map_max_load_num {
115+
return
116+
}
117+
118+
map_rehash(mut m, m.cap * 2)
119+
}
120+
121+
pub fun map_contains(m &Map, key string) bool {
122+
if m.cap == 0 {
123+
return false
124+
}
125+
126+
hash := map_hash(key)
127+
mut i := (hash % (m.cap as u32)) as i32
128+
for step := 0; step < m.cap; step += 1 {
129+
if not m.used[i] {
130+
return false
131+
}
132+
if m.hashes[i] == hash and map_key_eq(m.keys[i], key) {
133+
return true
134+
}
135+
i += 1
136+
if i == m.cap {
137+
i = 0
138+
}
139+
}
140+
return false
141+
}
142+
143+
pub fun map_set(mut m &Map, key string, val &void) {
144+
map_ensure_cap(mut m, m.length + 1)
145+
146+
hash := map_hash(key)
147+
mut i := (hash % (m.cap as u32)) as i32
148+
for step := 0; step < m.cap; step += 1 {
149+
if not m.used[i] {
150+
m.used[i] = true
151+
m.hashes[i] = hash
152+
m.keys[i] = key
153+
m.order.push(key)
154+
m.vals.set(i, val)
155+
m.length += 1
156+
return
157+
}
158+
159+
if m.hashes[i] == hash and map_key_eq(m.keys[i], key) {
160+
m.vals.set(i, val)
161+
return
162+
}
163+
164+
i += 1
165+
if i == m.cap {
166+
i = 0
167+
}
168+
}
169+
170+
map_rehash(mut m, m.cap * 2)
171+
map_set(mut m, key, val)
172+
}
173+
174+
pub fun map_get(m &Map, key string) &void {
175+
if m.cap == 0 {
176+
return m.zero
177+
}
178+
179+
hash := map_hash(key)
180+
mut i := (hash % (m.cap as u32)) as i32
181+
for step := 0; step < m.cap; step += 1 {
182+
if not m.used[i] {
183+
return m.zero
184+
}
185+
186+
if m.hashes[i] == hash and map_key_eq(m.keys[i], key) {
187+
return m.vals.get(i)
188+
}
189+
190+
i += 1
191+
if i == m.cap {
192+
i = 0
193+
}
194+
}
195+
196+
return m.zero
197+
}
198+
199+
pub fun (m Map) keys() []string {
200+
return m.order.copy()
201+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ fun test_type_init() {
55
mut m := map[string]string
66
m['a'] = 'A'
77
m['b'] = 'B'
8+
89
assert m.length == 2
910
assert m['b'] == 'B'
11+
12+
m['c'] = 'C'
13+
assert m.keys() == ['a', 'b', 'c']
1014
}
1115

1216
fun test_literal_init() {
1317
m := map{
1418
'a': 'A'
1519
'b': 'B'
1620
}
21+
1722
assert m.length == 2
1823
assert m['b'] == 'B'
1924
}

lib/hash/fnv1a/fnv1a.bt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Lukas Neubert <lukas.neubert@proton.me>
1+
// SPDX-FileCopyrightText: Lukas Neubert
22
// SPDX-License-Identifier: MIT
33

44
package fnv1a

tests/for/for_in_test.bt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Lukas Neubert <lukas.neubert@proton.me>
1+
// SPDX-FileCopyrightText: Lukas Neubert
22
// SPDX-License-Identifier: MIT
33

44
fun test_array() {

tests/for/labels_test.bt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Lukas Neubert <lukas.neubert@proton.me>
1+
// SPDX-FileCopyrightText: Lukas Neubert
22
// SPDX-License-Identifier: MIT
33

44
fun test_labelled_break_and_continue() {

0 commit comments

Comments
 (0)