-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathlib_stringify.lua
273 lines (251 loc) · 7.88 KB
/
lib_stringify.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
local function _stringifyFlatItem(obj)
local t = type(obj)
return t == 'table' or t == 'function' or t == 'thread' or t == 'userdata'
end
local function _stringifyFlatArray(obj, isArray, size)
if isArray then
for i = 1, size do
if _stringifyFlatItem(obj[i]) then return false end
end
else
for k, v in pairs(obj) do
if _stringifyFlatItem(k) or _stringifyFlatItem(v) then return false end
end
end
return true
end
local function _stringifyKey(out, ptr, obj, fnFullStringfy, lineBreak, depthLimit)
local objType = type(obj)
if objType == 'number' or objType == 'boolean' then
out[ptr] = '['
out[ptr + 1] = tostring(obj)
out[ptr + 2] = ']'
return ptr + 3
end
if objType == 'string' then
out[ptr] = string.match(obj, '^[%a_][%w_]*$') and obj or string.format('[%q]', obj)
else
out[ptr] = '['
ptr = fnFullStringfy(out, ptr + 1, obj, lineBreak, depthLimit)
out[ptr] = ']'
end
return ptr + 1
end
local function _stringifyFFI(out, ptr, obj)
return obj:__stringify(out, ptr)
end
local _svst, _svsp = nil, {}
local function _stringify(out, ptr, obj, lineBreak, depthLimit)
local objType = type(obj)
if objType == 'number' or objType == 'boolean' then
out[ptr] = tostring(obj)
return ptr + 1
end
if objType == 'string' then
out[ptr] = string.format('%q', obj)
return ptr + 1
end
if objType == 'table' then
if not _svst then
error('Invalid call', 2)
end
if _svst[obj] then
out[ptr] = lineBreak and '{ type = "circular reference" }' or '{type="circular reference"}'
return ptr + 1
end
if depthLimit < 0 then
out[ptr] = lineBreak and '{ type = "depth limit reached" }' or '{type="depth limit reached"}'
return ptr + 1
end
if type(obj.__stringify) == 'function' then
_svst[obj] = true
local r = obj:__stringify(out, ptr, lineBreak, depthLimit - 1)
local q = type(r)
if q == 'number' then
ptr = r
elseif type(r) == 'string' then
out[ptr] = r
ptr = ptr + 1
else
error('Method __stringify should either write string to provided table at a given position and return new position, or return a string', 2)
end
_svst[obj] = nil
return ptr
end
if next(obj) == nil then
out[ptr] = '{}'
return ptr + 1
end
local isArray = table.isArray(obj)
local size = #obj
local isFlat = lineBreak and _stringifyFlatArray(obj, isArray, size)
local comma, tabChild
if not lineBreak then
out[ptr], comma, tabChild = '{', ',', nil
elseif isFlat then
out[ptr], comma, tabChild = '{ ', ', ', lineBreak
else
tabChild = lineBreak .. ' '
comma = ',' .. tabChild
out[ptr] = '{'
out[ptr + 1] = tabChild
ptr = ptr + 1
end
_svst[obj] = true
if isArray then
for i = next(obj), size do
ptr = _stringify(out, ptr + 1, obj[i], tabChild, depthLimit - 1)
out[ptr] = comma
end
else
local h = 0 -- largest key of array-style elements
for i = obj[0] and 0 or 1, size do
if obj[i] == nil then break end
h = i
ptr = _stringify(out, ptr + 1, obj[i], tabChild, depthLimit - 1)
out[ptr] = comma
end
for k, v in pairs(obj) do
if type(k) ~= 'number' or k > h then
ptr = _stringifyKey(out, ptr + 1, k, _stringify, tabChild, depthLimit - 3)
out[ptr] = lineBreak and ' = ' or '='
ptr = _stringify(out, ptr + 1, v, tabChild, depthLimit - 1)
out[ptr] = comma
end
end
end
-- _svst[obj] = nil
out[ptr] = isFlat and ' }' or lineBreak and lineBreak..'}' or '}' -- replace last comma by }
return ptr + 1
end
if objType == 'cdata' then
local comma = lineBreak and ', ' or ','
--[[? for (let [type, fields] of [
[ 'vec2', ['x', 'y'] ],
[ 'vec3', ['x', 'y', 'z'] ],
[ 'vec4', ['x', 'y', 'z', 'w'] ],
[ 'quat', ['x', 'y', 'z', 'w'] ],
[ 'rgb', ['r', 'g', 'b'] ],
[ 'rgbm', ['r', 'g', 'b', 'mult'] ],
[ 'hsv', ['h', 's', 'v'] ],
[ 'refbool', ['value'] ],
[ 'refnumber', ['value'] ],
]) out(` if ${type}.is${type}(obj) then
if rawequal(obj, ${type}) then out[ptr] = 'nil' return ptr + 1 end
if ${fields.map(x => `obj.${x} == 0`).join(' and ')} then out[ptr] = '${type}()' return ptr + 1 end
out[ptr] = '${type}('
${fields.map((x, i) => ` out[ptr + ${i * 2 + 1}] = tostring(obj.${x})\n out[ptr + ${i * 2 + 2}] = ${i == fields.length - 1 ? `')'` : 'comma'}`).join('\n')}
return ptr + ${fields.length * 2 + 1}
end
`) ?]]
_svst[obj] = true
local s, v = pcall(_stringifyFFI, out, ptr, obj)
_svst[obj] = nil
if s then
return v
else
out[ptr] = lineBreak and '{ type = "cdata", tostring = ' or '{type="cdata",tostring='
out[ptr + 1] = string.format('%q', tostring(obj))
out[ptr + 2] = lineBreak and ' }' or '}'
return ptr + 3
end
end
if objType == 'nil' then
out[ptr] = 'nil'
return ptr + 1
end
-- can’t really stringify these, but let’s at least give back something
local fallback
if objType == 'function' then
local info = (debug and debug.getinfo or _dbg)(obj)
fallback = string.format(lineBreak and '{ type = "function", name = %q, source = %q, what = %q }' or '{type="function",name=%q,source=%q,what=%q}', info.name, info.source, info.what)
else
fallback = string.format(lineBreak and '{ type = %q, tostring = %q }' or '{type=%q,tostring=%q}', objType, tostring(obj))
end
out[ptr] = fallback
return ptr + 1
end
local _strt = nil
local _strp, _strn = {}, 0
local _penp, _penn = {}, 0
local _pent = {
__index = function(_, k)
local env = rawget(_, 'env')
if env and env[k] then return env[k] end
if _strt[k] then return _strt[k] end
error('Not available: '..tostring(k), 2)
end
}
local function _stringifyParse(v, env)
if type(v) ~= 'string' then error('String is required', 2) end
if not _strt then
_strt = { vec2 = vec2, vec3 = vec3, vec4 = vec4, quat = quat, rgb = rgb, rgbm = rgbm, hsv = hsv, refbool = refbool, refnumber = refnumber }
end
local s = _penn
local o = s > 0 and _penp[s] or setmetatable({ env = env }, _pent)
if s > 0 then _penn, o.env = s - 1, env end
local f, e = load('return '..v, 'stringify.parse', 't', o)
if not f then error(e, 2) end
local r = f()
s = _penn + 1
_penp[s], _penn = o, s
return r
end
--[[? if (!ctx.flags || !ctx.flags.targetPreprocessor){ out(]]
__definitions()
--[[) } ?]]
local function _call(v, compact, depthLimit)
local q = _svst == nil
if q then
_svst = _svsp
end
local s = _strn -- pool to reuse memory
local o = s > 0 and _strp[s] or {}
if s > 0 then _strn = s - 1 end
_stringify(o, 1, v, not compact and '\n' or nil, depthLimit or 20)
local r = table.concat(o)
s = _strn + 1
_strp[s], _strn = o, s
table.clear(o)
if q then
_svst = nil
table.clear(_svsp)
end
return r
end
return {
tryParse = function(v, env, fallback)
local r, p = pcall(_stringifyParse, v, env)
if r and p ~= nil then return p end
return fallback
end,
parse = _stringifyParse,
register = function (n, v)
if n.__name then _strt[n.__name] = n
else _strt[n] = v end
end,
substep = _stringify,
call = _call,
_strCns = function (v)
-- For const() preprocessing, do not use anywhere else
local t = type(v)
if t == 'function' then return '' end
if t == 'number' or t == 'boolean' or t == 'string' or t == 'table' or t == 'nil' then return _call(v, true, 40) end
local o = {'('}
local q = _svst == nil
if q then
_svst = _svsp
end
local e = _stringify(o, 2, v, nil, 40)
if q then
_svst = nil
table.clear(_svsp)
end
o[e] = ')'
local r = table.concat(o)
if string.find(r, '",tostring="', 1, true) then
error('Unserializable item: '..table.concat(o), 2)
end
return r
end
}