Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / collect_lzw.lua
blob904ba9a75df2466d33cda5c0110c676c0591228c
1 QuestHelper_File["collect_lzw.lua"] = "Development Version"
2 QuestHelper_Loadtime["collect_lzw.lua"] = GetTime()
4 local Merger
5 local Bitstream
7 local function cleanup(tab)
8 for _, v in pairs(tab) do
9 QuestHelper:ReleaseTable(v)
10 end
11 QuestHelper:ReleaseTable(tab)
12 end
14 local function QH_LZW_Decompress(input, tokens, outbits, inputstatic)
15 local d = QuestHelper:CreateTable("lzw")
16 local i
17 for i = 0, tokens-1 do
18 d[i] = QuestHelper:CreateTable("lzw")
19 d[i][0] = string.char(i)
20 end
22 local dsize = tokens + 1 -- we use the "tokens" value as an EOF marker
24 if inputstatic then
25 local used = QuestHelper:CreateTable("lzw")
26 for _, v in ipairs(inputstatic) do
27 for i = #v, 2, -1 do
28 local subi = v:sub(1, i)
29 if not used[subi] then
30 used[subi] = true
32 d[bit.mod(dsize, tokens)][math.floor(dsize / tokens)] = subi
33 dsize = dsize + 1
34 else
35 break
36 end
37 end
38 end
39 QuestHelper:ReleaseTable(used)
40 end
42 local bits = 1
43 local nextbits = 2
45 while nextbits < dsize do bits = bits + 1; nextbits = nextbits * 2 end
47 local i = Bitstream.Input(input, outbits)
48 local rv = {}
50 local idlect = 0
52 local tok = i:depend(bits)
53 if tok == tokens then cleanup(d) return "" end -- Okay. There's nothing. We get it.
55 Merger.Add(rv, d[bit.mod(tok, tokens)][math.floor(tok / tokens)])
56 local w = d[bit.mod(tok, tokens)][math.floor(tok / tokens)]
57 while true do
58 if idlect == 25 then
59 QH_Timeslice_Yield()
60 idlect = 0
61 else
62 idlect = idlect + 1
63 end
65 dsize = dsize + 1 -- We haven't actually added the next element yet. However, we could in theory include it in the stream, so we need to adjust the number of bits properly.
66 if dsize > nextbits then
67 bits = bits + 1
68 nextbits = nextbits * 2
69 end
71 tok = i:depend(bits)
72 if tok == tokens then break end -- we're done!
74 local entry
75 if d[bit.mod(tok, tokens)][math.floor(tok / tokens)] then
76 entry = d[bit.mod(tok, tokens)][math.floor(tok / tokens)]
77 elseif tok == dsize - 1 then
78 entry = w .. w:sub(1, 1)
79 else
80 QuestHelper: Assert(false, "faaaail")
81 end
82 Merger.Add(rv, entry)
84 d[bit.mod(dsize - 1, tokens)][math.floor((dsize - 1) / tokens)] = w .. entry:sub(1, 1) -- Naturally, we're writing to one *less* than dsize, since we already incremented.
86 w = entry
87 end
89 cleanup(d)
91 return Merger.Finish(rv)
92 end
94 local function QH_LZW_Compress(input, tokens, outbits, inputstatic)
95 -- shared init code
96 local d = {}
97 local i
98 for i = 0, tokens-1 do
99 d[string.char(i)] = {[""] = i}
102 local dsize = tokens + 1 -- we use the "tokens" value as an EOF marker
104 if inputstatic then
105 for _, v in ipairs(inputstatic) do
106 local da = d[v:sub(1, 1)]
107 for i = #v, 2, -1 do
108 local b = v:sub(2, i)
109 if not da[b] then
110 da[b] = dsize
111 dsize = dsize + 1
112 else
113 break
119 local bits = 1
120 local nextbits = 2
122 while nextbits < dsize do bits = bits + 1; nextbits = nextbits * 2 end
124 local r = Bitstream.Output(outbits)
126 local idlect = 0
128 local w = ""
129 for ci = 1, #input do
130 if idlect == 100 then
131 QH_Timeslice_Yield()
132 idlect = 0
133 else
134 idlect = idlect + 1
137 local c = input:sub(ci, ci)
138 local wcp = w .. c
139 if d[wcp:sub(1, 1)][wcp:sub(2)] then
140 w = wcp
141 else
142 r:append(d[w:sub(1, 1)][w:sub(2)], bits)
143 d[wcp:sub(1, 1)][wcp:sub(2)] = dsize
144 dsize = dsize + 1
145 if dsize > nextbits then
146 bits = bits + 1
147 nextbits = nextbits * 2
149 w = c
153 if w ~= "" then
154 r:append(d[w:sub(1, 1)][w:sub(2)], bits)
156 dsize = dsize + 1 -- Our decompressor doesn't realize we're ending here, so it will have added a table entry for that last token. Sigh.
157 if dsize > nextbits then
158 bits = bits + 1
159 nextbits = nextbits * 2
163 r:append(tokens, bits)
165 local rst = r:finish()
166 QuestHelper: Assert(QH_LZW_Decompress(rst, tokens, outbits, inputstatic) == input) -- yay
168 return rst
171 local function mdict(inputdict)
172 local idc = QuestHelper:CreateTable("lzw mdict")
173 for i = 1, #inputdict do idc[inputdict:sub(i, i)] = strchar(i - 1) end
174 return idc
177 local function dictize(idcl, stt)
178 local im = QuestHelper:CreateTable("lzw dictize")
179 for i = 1, #stt do
180 local subl = idcl[stt:sub(i, i)]
181 QuestHelper: Assert(subl)
182 Merger.Add(im, subl)
184 local result = Merger.Finish(im)
185 QuestHelper:ReleaseTable(im)
186 return result
189 local function QH_LZW_Prepare(inputdict, inputstatic)
190 QuestHelper: Assert(inputdict and inputstatic)
192 local idc = mdict(inputdict)
194 local inpstat = {}
195 for _, v in ipairs(inputstatic) do
196 table.insert(inpstat, dictize(idc, v))
199 return idc, #inputdict, inpstat
202 local function QH_LZW_Compress_Dicts_Prepared(input, idprepped, idpreppedsize, outputdict, isprepped)
203 input = dictize(idprepped, input)
205 local bits, dsize = 1, 2
206 if not outputdict then bits = 8 else while dsize < #outputdict do bits = bits + 1 ; dsize = dsize * 2 end end
207 QuestHelper: Assert(not outputdict or #outputdict == dsize)
209 local comp = QH_LZW_Compress(input, idpreppedsize, bits, isprepped)
211 if outputdict then
212 local origcomp = comp
213 local im = {}
214 for i = 1, #origcomp do Merger.Add(im, outputdict:sub(strbyte(origcomp:sub(i, i)) + 1)) end
215 comp = Merger.Finish(im)
218 return comp
221 local function QH_LZW_Compress_Dicts(input, inputdict, outputdict, inputstatic)
222 local inproc = input
223 local inpstat = inputstatic
224 if inputdict then
225 local idc = mdict(inputdict)
227 inproc = dictize(idc, input)
229 if inputstatic then
230 inpstat = {}
231 for _, v in ipairs(inputstatic) do
232 table.insert(inpstat, dictize(idc, v))
236 QuestHelper:ReleaseTable(idc)
239 local bits, dsize = 1, 2
240 if not outputdict then bits = 8 else while dsize < #outputdict do bits = bits + 1 ; dsize = dsize * 2 end end
241 QuestHelper: Assert(not outputdict or #outputdict == dsize)
243 local comp = QH_LZW_Compress(inproc, inputdict and #inputdict or 256, bits, inpstat)
245 if outputdict then
246 local origcomp = comp
247 local im = {}
248 for i = 1, #origcomp do Merger.Add(im, outputdict:sub(strbyte(origcomp:sub(i, i)) + 1)) end
249 comp = Merger.Finish(im)
252 return comp
255 local function QH_LZW_Decompress_Dicts_Prepared(compressed, inputdict, outputdict, ispreppred) -- this is kind of backwards - we assume that "outputdict" is the dictionary that "compressed" is encoded in
256 QuestHelper: Assert(not outputdict)
257 QuestHelper: Assert(inputdict)
259 local decomp = QH_LZW_Decompress(compressed, #inputdict, 8, ispreppred)
261 local ov = {}
262 for i = 1, #decomp do
263 Merger.Add(ov, inputdict:sub(decomp:byte(i) + 1, decomp:byte(i) + 1))
265 return Merger.Finish(ov)
268 local function QH_LZW_Decompress_Dicts(compressed, inputdict, outputdict, inputstatic) -- this is kind of backwards - we assume that "outputdict" is the dictionary that "compressed" is encoded in
269 QuestHelper: Assert(not outputdict)
270 QuestHelper: Assert(inputdict)
272 local inpstat = inputstatic
273 if inputdict and inputstatic then
274 local idc = mdict(inputdict)
276 inpstat = {}
277 for _, v in ipairs(inputstatic) do
278 table.insert(inpstat, dictize(idc, v))
281 QuestHelper:ReleaseTable(idc)
284 local decomp = QH_LZW_Decompress(compressed, #inputdict, 8, inpstat)
286 local ov = {}
287 for i = 1, #decomp do
288 Merger.Add(ov, inputdict:sub(decomp:byte(i) + 1, decomp:byte(i) + 1))
290 return Merger.Finish(ov)
293 QH_LZW_Prepare_Arghhacky = QH_LZW_Prepare -- need to rig up a better mechanism for this really
294 QH_LZW_Decompress_Dicts_Arghhacky = QH_LZW_Decompress_Dicts -- need to rig up a better mechanism for this really
295 QH_LZW_Decompress_Dicts_Prepared_Arghhacky = QH_LZW_Decompress_Dicts_Prepared -- need to rig up a better mechanism for this really
297 function QH_Collect_LZW_Init(_, API)
298 Merger = API.Utility_Merger
299 QuestHelper: Assert(Merger)
301 Bitstream = API.Utility_Bitstream
302 QuestHelper: Assert(Bitstream)
304 API.Utility_LZW = {Compress = QH_LZW_Compress, Decompress = QH_LZW_Decompress, Compress_Dicts = QH_LZW_Compress_Dicts, Decompress_Dicts = QH_LZW_Decompress_Dicts, Prepare = QH_LZW_Prepare, Compress_Dicts_Prepared = QH_LZW_Compress_Dicts_Prepared, Decompress_Dicts_Prepared = QH_LZW_Decompress_Dicts_Prepared}
307 -- old debug code :)
309 --[[
310 print("hello")
312 QH_LZW_Compress("TOBEORNOTTOBEORTOBEORNOT", 256, 8)
315 --[[
316 QuestHelper:TextOut("lulz")
318 local inq = "ABABABABA"
319 local alpha = 253
320 local bits = 7
322 str = QH_LZW_Compress(inq, alpha, bits)
323 tvr = ""
324 for i = 1, #str do
325 tvr = tvr .. string.format("%d ", strbyte(str, i))
327 QuestHelper:TextOut(tvr)
329 ret = QH_LZW_Decompress(str, alpha, bits)
330 QuestHelper:TextOut(ret)
332 QuestHelper: Assert(inq == ret)