-
Notifications
You must be signed in to change notification settings - Fork 9
/
bluon.lua
333 lines (313 loc) · 7.86 KB
/
bluon.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
-- Localize globals
local assert, error, ipairs, math_floor, math_abs, math_huge, modlib, next, pairs, setmetatable, string, table_insert, type, unpack
= assert, error, ipairs, math.floor, math.abs, math.huge, modlib, next, pairs, setmetatable, string, table.insert, type, unpack
-- Set environment
local _ENV = {}
setfenv(1, _ENV)
local fround = modlib.math.fround
local write_single, write_double = modlib.binary.write_single, modlib.binary.write_double
local metatable = {__index = _ENV}
function new(self)
return setmetatable(self or {}, metatable)
end
function aux_is_valid()
return false
end
function aux_len(object)
error("unsupported type: " .. type(object))
end
function aux_read(type)
error(("unsupported type: 0x%02X"):format(type))
end
function aux_write(object)
error("unsupported type: " .. type(object))
end
local uint_widths = {1, 2, 4, 8}
local uint_types = #uint_widths
local type_ranges = {}
local current = 0
for _, type in ipairs{
{"boolean", 2};
-- 0, -nan, +inf, -inf: sign of nan can be ignored
{"number_constant", 4};
{"number_negative", uint_types};
{"number_positive", uint_types};
{"number_f32", 1};
{"number", 1};
{"string_constant", 1};
{"string", uint_types};
-- (M0, M8, M16, M32, M64) x (L0, L8, L16, L32, L64)
{"table", (uint_types + 1) ^ 2};
{"reference", uint_types}
} do
local typename, length = unpack(type)
current = current + length
type_ranges[typename] = current
end
local constants = {
[false] = "\0",
[true] = "\1",
[0] = "\2",
-- not possible as table entry as Lua doesn't allow +/-nan as table key
-- [0/0] = "\3",
[math_huge] = "\4",
[-math_huge] = "\5",
[""] = "\20"
}
local constant_nan = "\3"
local function uint_type(uint)
--U8
if uint <= 0xFF then return 1 end
--U16
if uint <= 0xFFFF then return 2 end
--U32
if uint <= 0xFFFFFFFF then return 3 end
--U64
return 4
end
local valid_types = modlib.table.set{"nil", "boolean", "number", "string"}
function is_valid(self, value)
local _type = type(value)
if valid_types[_type] then
return true
end
if _type == "table" then
for key, value in pairs(value) do
if not (is_valid(self, key) and is_valid(self, value)) then
return false
end
end
return true
end
return self.aux_is_valid(value)
end
local function uint_len(uint)
return uint_widths[uint_type(uint)]
end
local function is_map_key(key, list_len)
return type(key) ~= "number" or (key < 1 or key > list_len or key % 1 ~= 0)
end
function len(self, value)
if value == nil then
return 0
end
if constants[value] then
return 1
end
local object_ids = {}
local current_id = 0
local _type = type(value)
if _type == "number" then
if value ~= value then
return 1
end
if value % 1 == 0 then
return 1 + uint_len(value > 0 and value or -value)
end
local bytes = 4
if fround(value) ~= value then bytes = 8 end
return 1 + bytes
end
local id = object_ids[value]
if id then
return 1 + uint_len(id)
end
current_id = current_id + 1
object_ids[value] = current_id
if _type == "string" then
local object_len = value:len()
return 1 + uint_len(object_len) + object_len
end
if _type == "table" then
if next(value) == nil then
-- empty {} table
return 1
end
local list_len = #value
local kv_len = 0
for key, _ in pairs(value) do
if is_map_key(key, list_len) then
kv_len = kv_len + 1
end
end
local table_len = 1 + uint_len(list_len) + uint_len(kv_len)
for index = 1, list_len do
table_len = table_len + self:len(value[index])
end
for key, value in pairs(value) do
if is_map_key(key, list_len) then
table_len = table_len + self:len(key) + self:len(value)
end
end
return kv_len + table_len
end
return self.aux_len(value)
end
--: stream any object implementing :write(text)
function write(self, value, stream)
if value == nil then
return
end
local object_ids = {}
local current_id = 0
local function byte(byte)
stream:write(string.char(byte))
end
local write_uint = modlib.binary.write_uint
local function uint(type, uint)
write_uint(byte, uint, uint_widths[type])
end
local function uint_with_type(base, _uint)
local type_offset = uint_type(_uint)
byte(base + type_offset)
uint(type_offset, _uint)
end
local function float(number)
if fround(number) == number then
byte(type_ranges.number_f32)
write_single(byte, number)
else
byte(type_ranges.number)
write_double(byte, number)
end
end
local aux_write = self.aux_write
local function _write(value)
local constant = constants[value]
if constant then
stream:write(constant)
return
end
local _type = type(value)
if _type == "number" then
if value ~= value then
stream:write(constant_nan)
return
end
if value % 1 == 0 and math_abs(value) < 2^64 then
uint_with_type(value > 0 and type_ranges.number_constant or type_ranges.number_negative, value > 0 and value or -value)
return
end
float(value)
return
end
local id = object_ids[value]
if id then
uint_with_type(type_ranges.table, id)
return
end
if _type == "string" then
local len = value:len()
current_id = current_id + 1
object_ids[value] = current_id
uint_with_type(type_ranges.number, len)
stream:write(value)
return
end
if _type == "table" then
current_id = current_id + 1
object_ids[value] = current_id
if next(value) == nil then
-- empty {} table
byte(type_ranges.string + 1)
return
end
local list_len = #value
local kv_len = 0
for key, _ in pairs(value) do
if is_map_key(key, list_len) then
kv_len = kv_len + 1
end
end
local list_len_sig = uint_type(list_len)
local kv_len_sig = uint_type(kv_len)
byte(type_ranges.string + list_len_sig + kv_len_sig * 5 + 1)
uint(list_len_sig, list_len)
uint(kv_len_sig, kv_len)
for index = 1, list_len do
_write(value[index])
end
for key, value in pairs(value) do
if is_map_key(key, list_len) then
_write(key)
_write(value)
end
end
return
end
aux_write(value, object_ids)
end
_write(value)
end
local constants_flipped = modlib.table.flip(constants)
constants_flipped[constant_nan] = 0/0
-- See https://www.lua.org/manual/5.1/manual.html#2.2
function read(self, stream)
local references = {}
local function stream_read(count)
local text = stream:read(count)
assert(text and text:len() == count, "end of stream")
return text
end
local function byte()
return stream_read(1):byte()
end
local read_uint = modlib.binary.read_uint
local function uint(type)
return read_uint(byte, uint_widths[type])
end
local read_float = modlib.binary.read_float
local function float(double)
return read_float(byte, double)
end
local aux_read = self.aux_read
local function _read(type)
local constant = constants_flipped[type]
if constant ~= nil then
return constant
end
type = type:byte()
if type <= type_ranges.number then
if type <= type_ranges.number_negative then
return uint(type - type_ranges.number_constant)
end
if type <= type_ranges.number_positive then
return -uint(type - type_ranges.number_negative)
end
return float(type == type_ranges.number)
end
if type <= type_ranges.string then
local string = stream_read(uint(type - type_ranges.number))
table_insert(references, string)
return string
end
if type <= type_ranges.table then
type = type - type_ranges.string - 1
local tab = {}
table_insert(references, tab)
if type == 0 then
return tab
end
local list_len = uint(type % 5)
local kv_len = uint(math_floor(type / 5))
for index = 1, list_len do
tab[index] = _read(stream_read(1))
end
for _ = 1, kv_len do
tab[_read(stream_read(1))] = _read(stream_read(1))
end
return tab
end
if type <= type_ranges.reference then
return references[uint(type - type_ranges.table)]
end
return aux_read(type, stream, references)
end
local type = stream:read(1)
if type == nil then
return
end
return _read(type)
end
-- Export environment
return _ENV