-
Notifications
You must be signed in to change notification settings - Fork 9
/
luon.lua
207 lines (191 loc) · 5.21 KB
/
luon.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
-- Lua module to serialize values as Lua code
local assert, error, rawget, pairs, pcall, type, setfenv, setmetatable, select, loadstring, loadfile
= assert, error, rawget, pairs, pcall, type, setfenv, setmetatable, select, loadstring, loadfile
local table_concat, string_format, math_huge
= table.concat, string.format, math.huge
local count_objects = modlib.table.count_objects
local is_identifier = modlib.text.is_identifier
local function quote(string)
return string_format("%q", string)
end
local _ENV = {}
setfenv(1, _ENV)
local metatable = {__index = _ENV}
_ENV.metatable = metatable
function new(self)
return setmetatable(self, metatable)
end
function aux_write(_self, _object)
-- returns reader, arguments
return
end
aux_read = {}
function write(self, value, write)
-- TODO evaluate custom aux. writers *before* writing for circular structs
local reference, refnum = "1", 1
-- [object] = reference
local references = {}
-- Circular tables that must be filled using `table[key] = value` statements
local to_fill = {}
-- TODO (?) sort objects by count, give frequently referenced objects shorter references
for object, count in pairs(count_objects(value)) do
local type_ = type(object)
-- Object must appear more than once. If it is a string, the reference has to be shorter than the string.
if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then
if refnum == 1 then
write"local _={};" -- initialize reference table
end
write"_["
write(reference)
write"]="
if type_ == "table" then
write"{}"
elseif type_ == "string" then
write(quote(object))
end
write";"
references[object] = reference
if type_ == "table" then
to_fill[object] = reference
end
refnum = refnum + 1
reference = string_format("%d", refnum)
end
end
-- Used to decide whether we should do "key=..."
local function use_short_key(key)
return not references[key] and type(key) == "string" and is_identifier(key)
end
local function dump(value)
-- Primitive types
if value == nil then
return write"nil"
end if value == true then
return write"true"
end if value == false then
return write"false"
end
local type_ = type(value)
if type_ == "number" then
-- Explicit handling of special values for forwards compatibility
if value ~= value then -- nan
return write"0/0"
end if value == math_huge then
return write"1/0"
end if value == -math_huge then
return write"-1/0"
end
return write(string_format("%.17g", value))
end
-- Reference types: table and string
local ref = references[value]
if ref then
-- Referenced
write"_["
write(ref)
return write"]"
end if type_ == "string" then
return write(quote(value))
end if type_ == "table" then
write"{"
-- First write list keys:
-- Don't use the table length #value here as it may horribly fail
-- for tables which use large integers as keys in the hash part;
-- stop at the first "hole" (nil value) instead
local len = 0
local first = true -- whether this is the first entry, which may not have a leading comma
while true do
local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
if v == nil then break end
if first then first = false else write(",") end
dump(v)
len = len + 1
end
-- Now write map keys ([key] = value)
for k, v in pairs(value) do
-- We have written all non-float keys in [1, len] already
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
if first then first = false else write(",") end
if use_short_key(k) then
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
end
end
return write"}"
end
-- TODO move aux_write to start, to allow dealing with metatables etc.?
return (function(func, ...)
-- functions are the only way to deal with varargs
if not func then
return error("unsupported type: " .. type_)
end
write(func)
write"("
local n = select("#", ...)
for i = 1, n - 1 do
dump(select(i, ...))
write","
end
if n > 0 then
dump(select(n, ...))
end
write")"
end)(self:aux_write(value))
end
-- Write the statements to fill circular tables
for table, ref in pairs(to_fill) do
for k, v in pairs(table) do
write"_["
write(ref)
write"]"
if use_short_key(k) then
write"."
write(k)
else
write"["
dump(k)
write"]"
end
write"="
dump(v)
write";"
end
end
write"return "
dump(value)
end
function write_file(self, value, file)
return self:write(value, function(text)
file:write(text)
end)
end
function write_string(self, value)
local rope = {}
self:write(value, function(text)
rope[#rope + 1] = text
end)
return table_concat(rope)
end
function read(self, ...)
local read = assert(...)
-- math.huge was serialized to inf, 0/0 was serialized to -nan by `%.17g`
setfenv(read, setmetatable({inf = math_huge, nan = 0/0}, {__index = self.aux_read}))
local success, value_or_err = pcall(read)
if success then
return value_or_err
end
return nil, value_or_err
end
function read_file(self, path)
return self:read(loadfile(path))
end
function read_string(self, string)
return self:read(loadstring(string))
end
return _ENV