# text_intern.ludic - a kept buffer's bytes as ONE string per distinct text: looked up by content # (FNV-1a, compared in place), copied out only the first time. Every string handed on is the # table's and never changes, so a memo keyed on it stays right; past `most` texts a plain copy export property StrTable { hs: words = null # open addressing: each slot's hash, and its entry + 1 at: words = null ss: []string = new []string most: int = 8192 } export function strs_new(most: int) -> StrTable { let tb = new StrTable tb.most = Math.max(most, 1) var cap = 16 while cap < tb.most * 2 { cap = cap * 2 } tb.hs = words(cap) tb.at = words(cap) return tb } export function strs_count(tb: StrTable) -> int { return len(tb.ss) } function sb_hash(sb: StrBuf) -> int { var h = -2128831035 for i in 0 .. sb.n { h = (h ^ sb.b[i]) * 16777619 } return h } function sb_same(sb: StrBuf, s: string) -> bool { let sp: string = s if len(sp) != sb.n { return false } for i in 0 .. sb.n { if sp[i] != sb.b[i] { return false } } return true } # the table's string for the buffer's bytes @alloc_ok("intern: one string per distinct text, up to the table's most, then kept") export function sb_intern(sb: StrBuf, tb: StrTable) -> string { let h = sb_hash(sb) let mask = len(tb.hs) - 1 var slot = h & mask while tb.at[slot] != 0 { let e = tb.at[slot] - 1 if tb.hs[slot] == h and sb_same(sb, tb.ss[e]) { return tb.ss[e] } slot = (slot + 1) & mask } let s = text_of(sb.b, sb.n) if len(tb.ss) >= tb.most { Mem.over("a StrTable past its most (sb_intern)") # 25.5a: a full table is a failure, not a copy per call return s } push(tb.ss, s) tb.hs[slot] = h tb.at[slot] = len(tb.ss) return s }