# ============================================================================ # lres.ludic — the reader behind map-scoped tables (`@PerMap registry R of T from "file.lres"`). # # A @PerMap registry's rows are not compiled in: they are read when a map loads, or a chunk comes # in, from `//`. This file reads one .lres - the grammar the compiler's # resource files use: entries `key { field: value, ... }`, nested records `{ ... }`, lists `[ ... ]`, # "strings" (\n \r \t \0 and \x for any other x), text keys k"a.b" and kn"a.b", numbers (-3, 0x1F, 1.5, 2e-3), true / false, a # constant by NAME, `fn name`, and `#` comments to the end of a line; commas and newlines separate. # # ludicc splices it when a program declares a @PerMap registry, and writes the typed fill for each # record (`r__fill_T`, frontend/permap_gen.ludic) on top of the few questions below. # # NOTHING HERE ALLOCATES PAST ITS HIGH WATER. A tree is a flat pool of nodes kept as parallel # lists that are reused from one file to the next (`nn` is how many are in use); the file's bytes go # into a buffer that grows only when a bigger file comes; a string is unescaped into a kept scratch # buffer and interned (intern() copies a text once and hands the same one back after), so a string # field is safe to keep; the path being opened is built in a kept buffer, so no concatenation. Only # an error's message is made on the heap, and only when a file is wrong. # ============================================================================ numbers float const LRES_K_REC: int = 1 const LRES_K_LIST: int = 2 const LRES_K_INT: int = 3 const LRES_K_FLOAT: int = 4 const LRES_K_STR: int = 5 const LRES_K_BOOL: int = 6 const LRES_K_NAME: int = 7 const LRES_K_FN: int = 8 const LRES_K_KEY: int = 9 # k"pause.resume" / kn"catch.count": a text key, kept after its marker byte # one reader: the file, its tree, the program's constants by name, and the first error property LresTree { buf: pointer = null # the file's bytes, NUL-terminated; grows to the largest file read bcap: int = 0 n: int = 0 # bytes in the file being read sb: pointer = null # a string being unescaped, before it is interned scap: int = 0 pb: pointer = null # the path being opened, NUL-terminated pcap: int = 0 plen: int = 0 kind: []int = new []int # per node: LRES_K_* ival: []int = new []int # an int, a bool (0/1) fval: []float = new []float # a float (an int's value too) sval: []string = new []string # a string, a name, a fn's name (interned) name: []string = new []string # an entry's key, a field's name ("" in a list) local: bool = false # a chunk's file: an entry's key is left in the file (koff, klen), never interned koff: []int = new []int # per entry, when local: where its key starts in buf ... klen: []int = new []int # ... and how long it is kid: []int = new []int # the first child of a record or a list, -1 nxt: []int = new []int # the next sibling, -1 line: []int = new []int col: []int = new []int nn: int = 0 # nodes in use top: int = -1 # the first entry ntop: int = 0 # how many entries p: int = 0 # the cursor, its line and where that line starts ln: int = 1 ls: int = 0 bad: bool = false err: string = "" # "file:line:col: what" - the first thing wrong cready: bool = false # the constants' index is built (on the first name read) ctab: string = "" # "NAME i 12;NAME f 1.5;..." sorted by name, written by the compiler coff: []int = new []int clen: []int = new []int cflt: []bool = new []bool civ: []int = new []int cfv: []float = new []float } # a chunk slot's own keys: a chunked table's keys are unique across a map (t0 .. t91842), so interning # them would fill the intern table as a player walks and keep every one for good. Row i's key lives # in buffer i, rewritten in place when the slot is refilled and grown only past the longest key it held. property LresKeys { bufs: []pointer = new []pointer caps: []int = new []int } # entry e's key as row i of a slot: in the slot's own buffer, or interned for a whole-map table (ks null) @alloc_ok("a slot's key buffer past the most rows or the longest key it has held (high water)") function lres_key_of(t: LresTree, e: int, ks: LresKeys, i: int) -> string { if ks == null or not t.local { return t.name[e] } let n = t.klen[e] if i >= len(ks.bufs) { var c = 16 while c < n + 1 { c = c * 2 } push(ks.bufs, bytes(c)) push(ks.caps, c) } else if ks.caps[i] < n + 1 { var c = ks.caps[i] * 2 while c < n + 1 { c = c * 2 } ks.bufs[i] = resize(ks.bufs[i], c) ks.caps[i] = c } let kb = ks.bufs[i] let buf = t.buf let a = t.koff[e] var j = 0 while j < n { kb[j] = buf[a + j] j += 1 } kb[n] = 0 let k: string = kb return k } # a key -> row index table over interned keys: open addressing, rebuilt in place on every load property LresHash { slot: []int = new []int # a row's index + 1; 0 is empty keys: []string = new []string mask: int = 0 } # ---- the path ------------------------------------------------------------------------------------ function lres_path_room(t: LresTree, need: int) -> void { if need <= t.pcap { return } var c = t.pcap * 2 if c < 256 { c = 256 } while c < need { c = c * 2 } if t.pb == null { t.pb = bytes(c) } else { t.pb = resize(t.pb, c) } t.pcap = c } function lres_path_begin(t: LresTree, root: string) -> void { t.plen = 0 lres_path_room(t, 1) let pb = t.pb pb[0] = 0 lres_path_add(t, root) } function lres_path_add(t: LresTree, s: string) -> void { let k = len(s) lres_path_room(t, t.plen + k + 1) let pb = t.pb var i = 0 while i < k { pb[t.plen + i] = s[i] i += 1 } t.plen += k pb[t.plen] = 0 } # a chunk's coordinate, as decimal: -3 is "-3" function lres_path_int(t: LresTree, v: int) -> void { lres_path_room(t, t.plen + 13) let pb = t.pb var x = v if x < 0 { pb[t.plen] = '-' t.plen += 1 x = 0 - x } let d0 = t.plen if x == 0 { pb[t.plen] = '0' t.plen += 1 } while x > 0 { pb[t.plen] = '0' + x % 10 t.plen += 1 x = x / 10 } var a = d0 var b = t.plen - 1 while a < b { let c = pb[a] pb[a] = pb[b] pb[b] = c a += 1 b -= 1 } pb[t.plen] = 0 } # ---- reading ------------------------------------------------------------------------------------- # the file at the path built: 1 read and parsed, 0 there is no such file, -1 it is wrong (t.err) function lres_read(t: LresTree) -> int { t.bad = false t.err = "" t.nn = 0 t.top = -1 t.ntop = 0 let f = file_open(t.pb, "rb") if f == null { return 0 } file_seek(f, 0, 2) var n = file_tell(f) file_seek(f, 0, 0) if n < 0 { n = 0 } if n + 1 > t.bcap { var c = t.bcap * 2 if c < 4096 { c = 4096 } while c < n + 1 { c = c * 2 } if t.buf == null { t.buf = bytes(c) } else { t.buf = resize(t.buf, c) } t.bcap = c } let buf = t.buf var got = 0 if n > 0 { got = file_read(f, buf, n) } file_close(f) if got < 0 { got = 0 } t.n = got buf[got] = 0 lres_parse(t) if t.bad { return -1 } return 1 } # a whole-map table's file that is not there: an error (a chunk's is an empty chunk) function lres_missing(t: LresTree) -> void { t.bad = true t.err = `{lres_path_text(t)}: there is no such file` } function lres_path_text(t: LresTree) -> string { if t.pb == null { return "" } return `{t.pb}` } # ---- errors -------------------------------------------------------------------------------------- # the first thing wrong, at a line and column of the file function lres_fail_at(t: LresTree, line: int, col: int, msg: string) -> void { if t.bad { return } t.bad = true t.err = `{lres_path_text(t)}:{line}:{col}: {msg}` } function lres_fail(t: LresTree, msg: string) -> void { lres_fail_at(t, t.ln, t.p - t.ls + 1, msg) } function lres_fail_node(t: LresTree, n: int, msg: string) -> void { lres_fail_at(t, t.line[n], t.col[n], msg) } function lres_ok(t: LresTree) -> bool { return not t.bad } function lres_error(t: LresTree) -> string { return t.err } # ---- the tree ------------------------------------------------------------------------------------ # a node, reused from an earlier file when the pool has one function lres_node(t: LresTree, kind: int, line: int, col: int) -> int { let i = t.nn if i < len(t.kind) { t.kind[i] = kind t.ival[i] = 0 t.fval[i] = 0.0 t.sval[i] = "" t.name[i] = "" t.kid[i] = -1 t.nxt[i] = -1 t.line[i] = line t.col[i] = col t.koff[i] = 0 t.klen[i] = 0 } else { push(t.kind, kind) push(t.ival, 0) push(t.fval, 0.0) push(t.sval, "") push(t.name, "") push(t.kid, -1) push(t.nxt, -1) push(t.line, line) push(t.col, col) push(t.koff, 0) push(t.klen, 0) } t.nn = i + 1 return i } function lres_ch(t: LresTree) -> int { if t.p >= t.n { return 0 } let buf = t.buf return buf[t.p] } function lres_is_alpha(c: int) -> bool { return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or c == '_' } function lres_is_digit(c: int) -> bool { return c >= '0' and c <= '9' } # blanks, newlines, commas and comments function lres_skip(t: LresTree) -> void { let buf = t.buf while t.p < t.n { let c = buf[t.p] if c == ' ' or c == 9 or c == 13 or c == ',' { t.p += 1 } else if c == 10 { t.p += 1 t.ln += 1 t.ls = t.p } else if c == '#' { while t.p < t.n and buf[t.p] != 10 { t.p += 1 } } else { return } } } # the scratch string, grown to hold n bytes and its NUL function lres_sb_room(t: LresTree, n: int) -> void { if n <= t.scap { return } var c = t.scap * 2 if c < 256 { c = 256 } while c < n { c = c * 2 } if t.sb == null { t.sb = bytes(c) } else { t.sb = resize(t.sb, c) } t.scap = c } # bytes a..b of the file, interned function lres_intern(t: LresTree, a: int, b: int) -> string { lres_sb_room(t, b - a + 1) let sb = t.sb let buf = t.buf var i = a while i < b { sb[i - a] = buf[i] i += 1 } sb[b - a] = 0 return intern(sb) } function lres_ident(t: LresTree) -> string { let a = lres_ident_at(t) if a < 0 { return "" } return lres_intern(t, a, t.p) } # a name's first byte, the cursor left past it; -1 (and the tree bad) when there is none function lres_ident_at(t: LresTree) -> int { let c = lres_ch(t) if not lres_is_alpha(c) { lres_fail(t, "a name was expected here") return -1 } let buf = t.buf let a = t.p while t.p < t.n and (lres_is_alpha(buf[t.p]) or lres_is_digit(buf[t.p])) { t.p += 1 } return a } function lres_link(t: LresTree, parent: int, last: int, k: int) -> void { if last < 0 { t.kid[parent] = k } else { t.nxt[last] = k } } # the whole file: its entries, each `key { ... }` function lres_parse(t: LresTree) -> void { t.p = 0 t.ln = 1 t.ls = 0 var last = -1 while not t.bad { lres_skip(t) if t.p >= t.n { return } let line = t.ln let col = t.p - t.ls + 1 let ka = lres_ident_at(t) if t.bad { return } let kb = t.p var key = "" if not t.local { key = lres_intern(t, ka, kb) } lres_skip(t) if lres_ch(t) != '{' { if t.local { key = lres_intern(t, ka, kb) } # a wrong file only: its message names the key lres_fail(t, `the entry {key} is its key and a record: {key} {{ field: value }}`) return } let e = lres_record(t) if t.bad { return } t.name[e] = key t.koff[e] = ka t.klen[e] = kb - ka t.line[e] = line t.col[e] = col if last < 0 { t.top = e } else { t.nxt[last] = e } last = e t.ntop += 1 } } function lres_record(t: LresTree) -> int { let r = lres_node(t, LRES_K_REC, t.ln, t.p - t.ls + 1) let oline = t.ln let ocol = t.p - t.ls + 1 t.p += 1 var last = -1 while not t.bad { lres_skip(t) let c = lres_ch(t) if c == '}' { t.p += 1 return r } if t.p >= t.n { lres_fail_at(t, oline, ocol, "this record is never closed with }") return r } let fline = t.ln let fcol = t.p - t.ls + 1 let fname = lres_ident(t) if t.bad { return r } lres_skip(t) if lres_ch(t) != ':' { lres_fail(t, `the field {fname} wants a ':' and its value`) return r } t.p += 1 lres_skip(t) let v = lres_value(t) if t.bad { return r } t.name[v] = fname t.line[v] = fline t.col[v] = fcol lres_link(t, r, last, v) last = v } return r } function lres_list(t: LresTree) -> int { let l = lres_node(t, LRES_K_LIST, t.ln, t.p - t.ls + 1) let oline = t.ln let ocol = t.p - t.ls + 1 t.p += 1 var last = -1 while not t.bad { lres_skip(t) let c = lres_ch(t) if c == ']' { t.p += 1 return l } if t.p >= t.n { lres_fail_at(t, oline, ocol, "this list is never closed with ]") return l } let v = lres_value(t) if t.bad { return l } lres_link(t, l, last, v) last = v } return l } function lres_value(t: LresTree) -> int { let c = lres_ch(t) if c == '{' { return lres_record(t) } if c == '[' { return lres_list(t) } if c == '"' { return lres_string(t) } if c == '-' or c == '+' or c == '.' or lres_is_digit(c) { return lres_number(t) } if c == 'k' and lres_key_ahead(t) { return lres_keylit(t) } if lres_is_alpha(c) { let line = t.ln let col = t.p - t.ls + 1 let id = lres_ident(t) if id == "true" or id == "false" { let b = lres_node(t, LRES_K_BOOL, line, col) if id == "true" { t.ival[b] = 1 } return b } if id == "fn" { let buf = t.buf while t.p < t.n and (buf[t.p] == ' ' or buf[t.p] == 9) { t.p += 1 } let f = lres_node(t, LRES_K_FN, line, col) t.sval[f] = lres_ident(t) return f } let k = lres_node(t, LRES_K_NAME, line, col) t.sval[k] = id return k } lres_fail(t, "a value was expected: a number, a \"string\", true, false, a NAME, fn name, { ... } or [ ... ]") return lres_node(t, LRES_K_INT, t.ln, t.p - t.ls + 1) } function lres_hex(c: int) -> int { if c >= '0' and c <= '9' { return c - '0' } if c >= 'a' and c <= 'f' { return c - 'a' + 10 } if c >= 'A' and c <= 'F' { return c - 'A' + 10 } return -1 } function lres_number(t: LresTree) -> int { let line = t.ln let col = t.p - t.ls + 1 let buf = t.buf let a = t.p var neg = false if buf[t.p] == '-' { neg = true t.p += 1 } else if buf[t.p] == '+' { t.p += 1 } if t.p + 1 < t.n and buf[t.p] == '0' and (buf[t.p + 1] == 'x' or buf[t.p + 1] == 'X') { t.p += 2 var v = 0 var any = false while t.p < t.n and (lres_hex(buf[t.p]) >= 0 or buf[t.p] == '_') { if buf[t.p] != '_' { v = v * 16 + lres_hex(buf[t.p]) any = true } t.p += 1 } if not any { lres_fail_at(t, line, col, "0x wants hex digits") } if neg { v = 0 - v } let h = lres_node(t, LRES_K_INT, line, col) t.ival[h] = v t.fval[h] = float(v) return lres_number_end(t, h) } var v = 0 var digits = 0 var fl = false while t.p < t.n { let c = buf[t.p] if lres_is_digit(c) { v = v * 10 + (c - '0') digits += 1 t.p += 1 } else if c == '_' { t.p += 1 } else if c == '.' and not fl and t.p + 1 < t.n and lres_is_digit(buf[t.p + 1]) { fl = true t.p += 1 } else if (c == 'e' or c == 'E') and digits > 0 { fl = true t.p += 1 if t.p < t.n and (buf[t.p] == '-' or buf[t.p] == '+') { t.p += 1 } } else { break } } if digits == 0 { lres_fail_at(t, line, col, "a number was expected") return lres_node(t, LRES_K_INT, line, col) } if not fl { if neg { v = 0 - v } let i = lres_node(t, LRES_K_INT, line, col) t.ival[i] = v t.fval[i] = float(v) return lres_number_end(t, i) } lres_sb_room(t, t.p - a + 1) let sb = t.sb var k = 0 var j = a while j < t.p { if buf[j] != '_' { sb[k] = buf[j] k += 1 } j += 1 } sb[k] = 0 let f = lres_node(t, LRES_K_FLOAT, line, col) t.fval[f] = Text.to_float(sb) return lres_number_end(t, f) } # a number runs into nothing: `12ab` is wrong, not 12 and a name function lres_number_end(t: LresTree, k: int) -> int { let c = lres_ch(t) if lres_is_alpha(c) or lres_is_digit(c) { lres_fail(t, "this is not a number") } return k } # k"..." or kn"...", the cursor on the k function lres_key_ahead(t: LresTree) -> bool { let buf = t.buf if t.p + 1 < t.n and buf[t.p + 1] == '"' { return true } return t.p + 2 < t.n and buf[t.p + 1] == 'n' and buf[t.p + 2] == '"' } # the key's text after its marker byte (1, or 2 for a plural), as the compiler writes k"..." function lres_keylit(t: LresTree) -> int { let line = t.ln let col = t.p - t.ls + 1 let buf = t.buf var mark = 1 t.p += 1 if buf[t.p] == 'n' { mark = 2 t.p += 1 } let s = lres_string_marked(t, mark) t.kind[s] = LRES_K_KEY t.line[s] = line t.col[s] = col return s } function lres_string(t: LresTree) -> int { return lres_string_marked(t, 0) } function lres_string_marked(t: LresTree, mark: int) -> int { let s = lres_node(t, LRES_K_STR, t.ln, t.p - t.ls + 1) let oline = t.ln let ocol = t.p - t.ls + 1 t.p += 1 let buf = t.buf var k = 0 if mark > 0 { lres_sb_room(t, 2) let sb0 = t.sb sb0[0] = mark k = 1 } while true { if t.p >= t.n { lres_fail_at(t, oline, ocol, "this string is never closed with \"") return s } var c = buf[t.p] if c == '"' { t.p += 1 break } if c == 10 { t.ln += 1 t.ls = t.p + 1 } if c == 92 and t.p + 1 < t.n { t.p += 1 c = buf[t.p] if c == 'n' { c = 10 } else if c == 'r' { c = 13 } else if c == 't' { c = 9 } else if c == '0' { c = 0 } } lres_sb_room(t, k + 2) let sb = t.sb sb[k] = c k += 1 t.p += 1 } lres_sb_room(t, k + 1) let sb = t.sb sb[k] = 0 t.sval[s] = intern(sb) return s } # ---- the questions a typed fill asks -------------------------------------------------------------- function lres_top(t: LresTree) -> int { return t.top } function lres_entries(t: LresTree) -> int { return t.ntop } function lres_kid(t: LresTree, n: int) -> int { return t.kid[n] } function lres_next(t: LresTree, n: int) -> int { return t.nxt[n] } function lres_name(t: LresTree, n: int) -> string { return t.name[n] } function lres_kind_text(k: int) -> string { if k == LRES_K_REC { return "a record" } if k == LRES_K_LIST { return "a list" } if k == LRES_K_INT { return "an int" } if k == LRES_K_FLOAT { return "a float" } if k == LRES_K_STR { return "a string" } if k == LRES_K_BOOL { return "true or false" } if k == LRES_K_NAME { return "a name" } if k == LRES_K_KEY { return "a key" } return "fn name" } function lres_want(t: LresTree, n: int, what: string) -> void { var who = t.name[n] if len(who) == 0 { who = "an element" } lres_fail_node(t, n, `{who} wants {what}, not {lres_kind_text(t.kind[n])}`) } function lres_int(t: LresTree, n: int) -> int { let k = t.kind[n] if k == LRES_K_INT { return t.ival[n] } if k == LRES_K_NAME { let c = lres_const(t, t.sval[n]) if c >= 0 and not t.cflt[c] { return t.civ[c] } if c >= 0 { lres_fail_node(t, n, `{t.sval[n]} is a float constant, and {t.name[n]} wants an int`) } else { lres_fail_node(t, n, `unknown constant {t.sval[n]}`) } return 0 } lres_want(t, n, "an int") return 0 } function lres_float(t: LresTree, n: int) -> float { let k = t.kind[n] if k == LRES_K_FLOAT or k == LRES_K_INT { return t.fval[n] } if k == LRES_K_NAME { let c = lres_const(t, t.sval[n]) if c >= 0 { if t.cflt[c] { return t.cfv[c] } return float(t.civ[c]) } lres_fail_node(t, n, `unknown constant {t.sval[n]}`) return 0.0 } lres_want(t, n, "a float") return 0.0 } function lres_bool(t: LresTree, n: int) -> bool { if t.kind[n] == LRES_K_BOOL { return t.ival[n] != 0 } lres_want(t, n, "true or false") return false } function lres_str(t: LresTree, n: int) -> string { if t.kind[n] == LRES_K_STR { return t.sval[n] } lres_want(t, n, "a \"string\"") return "" } # a Key field's value: k"..." (its marker kept, as the compiler writes the literal) function lres_key(t: LresTree, n: int) -> Key { if t.kind[n] == LRES_K_KEY { let p: pointer = t.sval[n] return p } lres_want(t, n, "a key k\"...\"") return null } # `fn name`: the name, which the fill resolves among the program's functions of the field's type function lres_fn(t: LresTree, n: int) -> string { if t.kind[n] == LRES_K_FN { return t.sval[n] } lres_want(t, n, "fn name") return "" } function lres_is_rec(t: LresTree, n: int) -> bool { if t.kind[n] == LRES_K_REC { return true } lres_want(t, n, "a record { ... }") return false } function lres_is_list(t: LresTree, n: int) -> bool { if t.kind[n] == LRES_K_LIST { return true } lres_want(t, n, "a list [ ... ]") return false } function lres_no_field(t: LresTree, n: int, rec: string) -> void { lres_fail_node(t, n, `{rec} has no field {t.name[n]}`) } function lres_no_fn(t: LresTree, n: int, ty: string) -> void { lres_fail_node(t, n, `there is no function {t.sval[n]} of type {ty}`) } function lres_dup(t: LresTree, n: int) -> void { var key = t.name[n] if t.local { key = lres_intern(t, t.koff[n], t.koff[n] + t.klen[n]) } # a wrong file only lres_fail_node(t, n, `the key {key} is written twice`) } # ---- the program's constants, by name ------------------------------------------------------------ # lres_consts__() is written by the compiler: every int and float constant it could evaluate, as # "NAME i 12;NAME f 1.5;", sorted by name. The index is made once, on the first name a file uses. function lres_consts_index(t: LresTree) -> void { t.cready = true let s = lres_consts__() t.ctab = s let n = len(s) var i = 0 while i < n { let a = i while i < n and s[i] != ' ' { i += 1 } let nl = i - a i += 1 let fl = i < n and s[i] == 'f' i += 2 let va = i while i < n and s[i] != ';' { i += 1 } push(t.coff, a) push(t.clen, nl) push(t.cflt, fl) if fl { lres_sb_room(t, i - va + 1) let sb = t.sb var j = va while j < i { sb[j - va] = s[j] j += 1 } sb[i - va] = 0 push(t.civ, 0) push(t.cfv, Text.to_float(sb)) } else { var v = 0 var neg = false var j = va if j < i and s[j] == '-' { neg = true j += 1 } while j < i { v = v * 10 + (s[j] - '0') j += 1 } if neg { v = 0 - v } push(t.civ, v) push(t.cfv, 0.0) } i += 1 } } # name against the table's entry c: <0, 0, >0 function lres_const_cmp(t: LresTree, name: string, c: int) -> int { let s = t.ctab let off = t.coff[c] let m = t.clen[c] let k = len(name) var i = 0 while i < k and i < m { let x = name[i] let y = s[off + i] if x != y { return x - y } i += 1 } return k - m } # the constant called name: its index in the table, or -1 function lres_const(t: LresTree, name: string) -> int { if not t.cready { lres_consts_index(t) } var lo = 0 var hi = len(t.coff) - 1 while lo <= hi { let mid = (lo + hi) / 2 let d = lres_const_cmp(t, name, mid) if d == 0 { return mid } if d < 0 { hi = mid - 1 } else { lo = mid + 1 } } return -1 } # ---- rows by key --------------------------------------------------------------------------------- function lres_hash_of(s: string) -> int { var h = -2128831035 let n = len(s) var i = 0 while i < n { h = (h ^ s[i]) * 16777619 i += 1 } return h } # empty, sized for n keys; the slots grow only past the most a table has ever had function lres_hash_reset(h: LresHash, n: int) -> void { var size = 16 while size < n * 2 { size = size * 2 } while len(h.slot) < size { push(h.slot, 0) push(h.keys, "") } var i = 0 while i < size { h.slot[i] = 0 h.keys[i] = "" i += 1 } h.mask = size - 1 } # key -> i; when the key is already there, the index it has (and nothing changes), else -1 function lres_hash_put(h: LresHash, key: string, i: int) -> int { var at = lres_hash_of(key) & h.mask while true { let v = h.slot[at] if v == 0 { h.slot[at] = i + 1 h.keys[at] = key return -1 } if h.keys[at] == key { return v - 1 } at = (at + 1) & h.mask } return -1 } # the row whose key is key, or -1 function lres_hash_get(h: LresHash, key: string) -> int { if h.mask == 0 { return -1 } var at = lres_hash_of(key) & h.mask while true { let v = h.slot[at] if v == 0 { return -1 } if h.keys[at] == key { return v - 1 } at = (at + 1) & h.mask } return -1 }