util-tab.lua /size: 32 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
util-tab
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to luat-lib.mkiv
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
utilities
=
utilities
or
{
}
10
utilities
.
tables
=
utilities
.
tables
or
{
}
11
local
tables
=
utilities
.
tables
12 13
local
format
,
gmatch
,
gsub
,
sub
=
string
.
format
,
string
.
gmatch
,
string
.
gsub
,
string
.
sub
14
local
concat
,
insert
,
remove
,
sort
=
table
.
concat
,
table
.
insert
,
table
.
remove
,
table
.
sort
15
local
setmetatable
,
getmetatable
,
tonumber
,
tostring
,
rawget
=
setmetatable
,
getmetatable
,
tonumber
,
tostring
,
rawget
16
local
type
,
next
,
rawset
,
tonumber
,
tostring
,
load
,
select
=
type
,
next
,
rawset
,
tonumber
,
tostring
,
load
,
select
17
local
lpegmatch
,
P
,
Cs
,
Cc
=
lpeg
.
match
,
lpeg
.
P
,
lpeg
.
Cs
,
lpeg
.
Cc
18
local
sortedkeys
,
sortedpairs
=
table
.
sortedkeys
,
table
.
sortedpairs
19
local
formatters
=
string
.
formatters
20
local
utftoeight
=
utf
.
toeight
21 22
local
splitter
=
lpeg
.
tsplitat
(
"
.
"
)
23 24
function
utilities
.
tables
.
definetable
(
target
,
nofirst
,
nolast
)
-- defines undefined tables
25
local
composed
=
nil
26
local
t
=
{
}
27
local
snippets
=
lpegmatch
(
splitter
,
target
)
28
for
i
=
1
,
#
snippets
-
(
nolast
and
1
or
0
)
do
29
local
name
=
snippets
[
i
]
30
if
composed
then
31
composed
=
composed
.
.
"
.
"
.
.
name
32
t
[
#
t
+
1
]
=
formatters
[
"
if not %s then %s = { } end
"
]
(
composed
,
composed
)
33
else
34
composed
=
name
35
if
not
nofirst
then
36
t
[
#
t
+
1
]
=
formatters
[
"
%s = %s or { }
"
]
(
composed
,
composed
)
37
end
38
end
39
end
40
if
composed
then
41
if
nolast
then
42
composed
=
composed
.
.
"
.
"
.
.
snippets
[
#
snippets
]
43
end
44
return
concat
(
t
,
"
\n
"
)
,
composed
-- could be shortcut
45
else
46
return
"
"
,
target
47
end
48
end
49 50
-- local t = tables.definedtable("a","b","c","d")
51 52
function
tables
.
definedtable
(
...
)
53
local
t
=
_G
54
for
i
=
1
,
select
(
"
#
"
,
...
)
do
55
local
li
=
select
(
i
,
...
)
56
local
tl
=
t
[
li
]
57
if
not
tl
then
58
tl
=
{
}
59
t
[
li
]
=
tl
60
end
61
t
=
tl
62
end
63
return
t
64
end
65 66
function
tables
.
accesstable
(
target
,
root
)
67
local
t
=
root
or
_G
68
for
name
in
gmatch
(
target
,
"
([^%.]+)
"
)
do
69
t
=
t
[
name
]
70
if
not
t
then
71
return
72
end
73
end
74
return
t
75
end
76 77
function
tables
.
migratetable
(
target
,
v
,
root
)
78
local
t
=
root
or
_G
79
local
names
=
lpegmatch
(
splitter
,
target
)
80
for
i
=
1
,
#
names
-1
do
81
local
name
=
names
[
i
]
82
t
[
name
]
=
t
[
name
]
or
{
}
83
t
=
t
[
name
]
84
if
not
t
then
85
return
86
end
87
end
88
t
[
names
[
#
names
]
]
=
v
89
end
90 91
function
tables
.
removevalue
(
t
,
value
)
-- todo: n
92
if
value
then
93
for
i
=
1
,
#
t
do
94
if
t
[
i
]
=
=
value
then
95
remove
(
t
,
i
)
96
-- remove all, so no: return
97
end
98
end
99
end
100
end
101 102
function
tables
.
replacevalue
(
t
,
oldvalue
,
newvalue
)
103
if
oldvalue
and
newvalue
then
104
for
i
=
1
,
#
t
do
105
if
t
[
i
]
=
=
oldvalue
then
106
t
[
i
]
=
newvalue
107
-- replace all, so no: return
108
end
109
end
110
end
111
end
112 113
function
tables
.
insertbeforevalue
(
t
,
value
,
extra
)
114
for
i
=
1
,
#
t
do
115
if
t
[
i
]
=
=
extra
then
116
remove
(
t
,
i
)
117
end
118
end
119
for
i
=
1
,
#
t
do
120
if
t
[
i
]
=
=
value
then
121
insert
(
t
,
i
,
extra
)
122
return
123
end
124
end
125
insert
(
t
,
1
,
extra
)
126
end
127 128
function
tables
.
insertaftervalue
(
t
,
value
,
extra
)
129
for
i
=
1
,
#
t
do
130
if
t
[
i
]
=
=
extra
then
131
remove
(
t
,
i
)
132
end
133
end
134
for
i
=
1
,
#
t
do
135
if
t
[
i
]
=
=
value
then
136
insert
(
t
,
i
+
1
,
extra
)
137
return
138
end
139
end
140
insert
(
t
,
#
t
+
1
,
extra
)
141
end
142 143
-- experimental
144 145
local
escape
=
Cs
(
Cc
(
'
"
'
)
*
(
(
P
(
'
"
'
)
/
'
""
'
+
P
(
1
)
)
^
0
)
*
Cc
(
'
"
'
)
)
146 147
function
table
.
tocsv
(
t
,
specification
)
148
if
t
and
#
t
>
0
then
149
local
result
=
{
}
150
local
r
=
{
}
151
specification
=
specification
or
{
}
152
local
fields
=
specification
.
fields
153
if
type
(
fields
)
~
=
"
string
"
then
154
fields
=
sortedkeys
(
t
[
1
]
)
155
end
156
local
separator
=
specification
.
separator
or
"
,
"
157
local
noffields
=
#
fields
158
if
specification
.
preamble
=
=
true
then
159
for
f
=
1
,
noffields
do
160
r
[
f
]
=
lpegmatch
(
escape
,
tostring
(
fields
[
f
]
)
)
161
end
162
result
[
1
]
=
concat
(
r
,
separator
)
163
end
164
for
i
=
1
,
#
t
do
165
local
ti
=
t
[
i
]
166
for
f
=
1
,
noffields
do
167
local
field
=
ti
[
fields
[
f
]
]
168
if
type
(
field
)
=
=
"
string
"
then
169
r
[
f
]
=
lpegmatch
(
escape
,
field
)
170
else
171
r
[
f
]
=
tostring
(
field
)
172
end
173
end
174
-- result[#result+1] = concat(r,separator)
175
result
[
i
+
1
]
=
concat
(
r
,
separator
)
176
end
177
return
concat
(
result
,
"
\n
"
)
178
else
179
return
"
"
180
end
181
end
182 183
-- local nspaces = utilities.strings.newrepeater(" ")
184
-- local escape = Cs((P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;" + P(1))^0)
185
--
186
-- local function toxml(t,d,result,step)
187
-- for k, v in sortedpairs(t) do
188
-- local s = nspaces[d]
189
-- local tk = type(k)
190
-- local tv = type(v)
191
-- if tv == "table" then
192
-- if tk == "number" then
193
-- result[#result+1] = format("%s<entry n='%s'>",s,k)
194
-- toxml(v,d+step,result,step)
195
-- result[#result+1] = format("%s</entry>",s,k)
196
-- else
197
-- result[#result+1] = format("%s<%s>",s,k)
198
-- toxml(v,d+step,result,step)
199
-- result[#result+1] = format("%s</%s>",s,k)
200
-- end
201
-- elseif tv == "string" then
202
-- if tk == "number" then
203
-- result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,lpegmatch(escape,v),k)
204
-- else
205
-- result[#result+1] = format("%s<%s>%s</%s>",s,k,lpegmatch(escape,v),k)
206
-- end
207
-- elseif tk == "number" then
208
-- result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,tostring(v),k)
209
-- else
210
-- result[#result+1] = format("%s<%s>%s</%s>",s,k,tostring(v),k)
211
-- end
212
-- end
213
-- end
214
--
215
-- much faster
216 217
local
nspaces
=
utilities
.
strings
.
newrepeater
(
"
"
)
218 219
local
function
toxml
(
t
,
d
,
result
,
step
)
220
local
r
=
#
result
221
for
k
,
v
in
sortedpairs
(
t
)
do
222
local
s
=
nspaces
[
d
]
-- inlining this is somewhat faster but gives more formatters
223
local
tk
=
type
(
k
)
224
local
tv
=
type
(
v
)
225
if
tv
=
=
"
table
"
then
226
if
tk
=
=
"
number
"
then
227
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<entry n='%s'>
"
]
(
s
,
k
)
228
toxml
(
v
,
d
+
step
,
result
,
step
)
229
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s</entry>
"
]
(
s
,
k
)
230
else
231
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<%s>
"
]
(
s
,
k
)
232
toxml
(
v
,
d
+
step
,
result
,
step
)
233
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s</%s>
"
]
(
s
,
k
)
234
end
235
elseif
tv
=
=
"
string
"
then
236
if
tk
=
=
"
number
"
then
237
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<entry n='%s'>%!xml!</entry>
"
]
(
s
,
k
,
v
,
k
)
238
else
239
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<%s>%!xml!</%s>
"
]
(
s
,
k
,
v
,
k
)
240
end
241
elseif
tk
=
=
"
number
"
then
242
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<entry n='%s'>%S</entry>
"
]
(
s
,
k
,
v
,
k
)
243
else
244
r
=
r
+
1
result
[
r
]
=
formatters
[
"
%s<%s>%S</%s>
"
]
(
s
,
k
,
v
,
k
)
245
end
246
end
247
end
248 249
-- function table.toxml(t,name,nobanner,indent,spaces)
250
-- local noroot = name == false
251
-- local result = (nobanner or noroot) and { } or { "<?xml version='1.0' standalone='yes' ?>" }
252
-- local indent = rep(" ",indent or 0)
253
-- local spaces = rep(" ",spaces or 1)
254
-- if noroot then
255
-- toxml( t, inndent, result, spaces)
256
-- else
257
-- toxml( { [name or "root"] = t }, indent, result, spaces)
258
-- end
259
-- return concat(result,"\n")
260
-- end
261 262
function
table
.
toxml
(
t
,
specification
)
263
specification
=
specification
or
{
}
264
local
name
=
specification
.
name
265
local
noroot
=
name
=
=
false
266
local
result
=
(
specification
.
nobanner
or
noroot
)
and
{
}
or
{
"
<?xml version='1.0' standalone='yes' ?>
"
}
267
local
indent
=
specification
.
indent
or
0
268
local
spaces
=
specification
.
spaces
or
1
269
if
noroot
then
270
toxml
(
t
,
indent
,
result
,
spaces
)
271
else
272
toxml
(
{
[
name
or
"
data
"
]
=
t
}
,
indent
,
result
,
spaces
)
273
end
274
return
concat
(
result
,
"
\n
"
)
275
end
276 277
-- also experimental
278 279
-- encapsulate(table,utilities.tables)
280
-- encapsulate(table,utilities.tables,true)
281
-- encapsulate(table,true)
282 283
function
tables
.
encapsulate
(
core
,
capsule
,
protect
)
284
if
type
(
capsule
)
~
=
"
table
"
then
285
protect
=
true
286
capsule
=
{
}
287
end
288
for
key
,
value
in
next
,
core
do
289
if
capsule
[
key
]
then
290
print
(
formatters
[
"
\ninvalid %s %a in %a
"
]
(
"
inheritance
"
,
key
,
core
)
)
291
os
.
exit
(
)
292
else
293
capsule
[
key
]
=
value
294
end
295
end
296
if
protect
then
297
for
key
,
value
in
next
,
core
do
298
core
[
key
]
=
nil
299
end
300
setmetatable
(
core
,
{
301
__index
=
capsule
,
302
__newindex
=
function
(
t
,
key
,
value
)
303
if
capsule
[
key
]
then
304
print
(
formatters
[
"
\ninvalid %s %a' in %a
"
]
(
"
overload
"
,
key
,
core
)
)
305
os
.
exit
(
)
306
else
307
rawset
(
t
,
key
,
value
)
308
end
309
end
310
}
)
311
end
312
end
313 314
-- best keep [%q] keys (as we have some in older applications i.e. saving user data (otherwise
315
-- we also need to check for reserved words)
316 317
if
JITSUPPORTED
then
318 319
local
f_hashed_string
=
formatters
[
"
[%Q]=%Q,
"
]
320
local
f_hashed_number
=
formatters
[
"
[%Q]=%s,
"
]
321
local
f_hashed_boolean
=
formatters
[
"
[%Q]=%l,
"
]
322
local
f_hashed_table
=
formatters
[
"
[%Q]=
"
]
323 324
local
f_indexed_string
=
formatters
[
"
[%s]=%Q,
"
]
325
local
f_indexed_number
=
formatters
[
"
[%s]=%s,
"
]
326
local
f_indexed_boolean
=
formatters
[
"
[%s]=%l,
"
]
327
local
f_indexed_table
=
formatters
[
"
[%s]=
"
]
328 329
local
f_ordered_string
=
formatters
[
"
%Q,
"
]
330
local
f_ordered_number
=
formatters
[
"
%s,
"
]
331
local
f_ordered_boolean
=
formatters
[
"
%l,
"
]
332 333
function
table
.
fastserialize
(
t
,
prefix
)
-- todo, move local function out
334 335
-- prefix should contain the =
336
-- not sorted
337
-- only number and string indices (currently)
338 339
local
r
=
{
type
(
prefix
)
=
=
"
string
"
and
prefix
or
"
return
"
}
340
local
m
=
1
341
local
function
fastserialize
(
t
,
outer
)
-- no mixes
342
local
n
=
#
t
343
m
=
m
+
1
344
r
[
m
]
=
"
{
"
345
if
n
>
0
then
346
local
v
=
t
[
0
]
347
if
v
then
348
local
tv
=
type
(
v
)
349
if
tv
=
=
"
string
"
then
350
m
=
m
+
1
r
[
m
]
=
f_indexed_string
(
0
,
v
)
351
elseif
tv
=
=
"
number
"
then
352
m
=
m
+
1
r
[
m
]
=
f_indexed_number
(
0
,
v
)
353
elseif
tv
=
=
"
table
"
then
354
m
=
m
+
1
r
[
m
]
=
f_indexed_table
(
0
)
355
fastserialize
(
v
)
356
m
=
m
+
1
r
[
m
]
=
f_indexed_table
(
0
)
357
elseif
tv
=
=
"
boolean
"
then
358
m
=
m
+
1
r
[
m
]
=
f_indexed_boolean
(
0
,
v
)
359
end
360
end
361
for
i
=
1
,
n
do
362
local
v
=
t
[
i
]
363
local
tv
=
type
(
v
)
364
if
tv
=
=
"
string
"
then
365
m
=
m
+
1
r
[
m
]
=
f_ordered_string
(
v
)
366
elseif
tv
=
=
"
number
"
then
367
m
=
m
+
1
r
[
m
]
=
f_ordered_number
(
v
)
368
elseif
tv
=
=
"
table
"
then
369
fastserialize
(
v
)
370
elseif
tv
=
=
"
boolean
"
then
371
m
=
m
+
1
r
[
m
]
=
f_ordered_boolean
(
v
)
372
end
373
end
374
end
375
-- hm, can't we avoid this ... lua should have a way to check if there
376
-- is a hash part
377
for
k
,
v
in
next
,
t
do
378
local
tk
=
type
(
k
)
379
if
tk
=
=
"
number
"
then
380
if
k
>
n
or
k
<
0
then
381
local
tv
=
type
(
v
)
382
if
tv
=
=
"
string
"
then
383
m
=
m
+
1
r
[
m
]
=
f_indexed_string
(
k
,
v
)
384
elseif
tv
=
=
"
number
"
then
385
m
=
m
+
1
r
[
m
]
=
f_indexed_number
(
k
,
v
)
386
elseif
tv
=
=
"
table
"
then
387
m
=
m
+
1
r
[
m
]
=
f_indexed_table
(
k
)
388
fastserialize
(
v
)
389
elseif
tv
=
=
"
boolean
"
then
390
m
=
m
+
1
r
[
m
]
=
f_indexed_boolean
(
k
,
v
)
391
end
392
end
393
else
394
local
tv
=
type
(
v
)
395
if
tv
=
=
"
string
"
then
396
m
=
m
+
1
r
[
m
]
=
f_hashed_string
(
k
,
v
)
397
elseif
tv
=
=
"
number
"
then
398
m
=
m
+
1
r
[
m
]
=
f_hashed_number
(
k
,
v
)
399
elseif
tv
=
=
"
table
"
then
400
m
=
m
+
1
r
[
m
]
=
f_hashed_table
(
k
)
401
fastserialize
(
v
)
402
elseif
tv
=
=
"
boolean
"
then
403
m
=
m
+
1
r
[
m
]
=
f_hashed_boolean
(
k
,
v
)
404
end
405
end
406
end
407
m
=
m
+
1
408
if
outer
then
409
r
[
m
]
=
"
}
"
410
else
411
r
[
m
]
=
"
},
"
412
end
413
return
r
414
end
415
return
concat
(
fastserialize
(
t
,
true
)
)
416
end
417 418
else
419 420
local
f_v
=
formatters
[
"
[%q]=%q,
"
]
421
local
f_t
=
formatters
[
"
[%q]=
"
]
422
local
f_q
=
formatters
[
"
%q,
"
]
423 424
function
table
.
fastserialize
(
t
,
prefix
)
-- todo, move local function out
425
local
r
=
{
type
(
prefix
)
=
=
"
string
"
and
prefix
or
"
return
"
}
426
local
m
=
1
427
local
function
fastserialize
(
t
,
outer
)
-- no mixes
428
local
n
=
#
t
429
m
=
m
+
1
430
r
[
m
]
=
"
{
"
431
if
n
>
0
then
432
local
v
=
t
[
0
]
433
if
v
then
434
m
=
m
+
1
435
r
[
m
]
=
"
[0]='
"
436
if
type
(
v
)
=
=
"
table
"
then
437
fastserialize
(
v
)
438
else
439
r
[
m
]
=
format
(
"
%q,
"
,
v
)
440
end
441
end
442
for
i
=
1
,
n
do
443
local
v
=
t
[
i
]
444
m
=
m
+
1
445
if
type
(
v
)
=
=
"
table
"
then
446
r
[
m
]
=
format
(
"
[%i]=
"
,
i
)
447
fastserialize
(
v
)
448
else
449
r
[
m
]
=
format
(
"
[%i]=%q,
"
,
i
,
v
)
450
end
451
end
452
end
453
-- hm, can't we avoid this ... lua should have a way to check if there
454
-- is a hash part
455
for
k
,
v
in
next
,
t
do
456
local
tk
=
type
(
k
)
457
if
tk
=
=
"
number
"
then
458
if
k
>
n
or
k
<
0
then
459
m
=
m
+
1
460
if
type
(
v
)
=
=
"
table
"
then
461
r
[
m
]
=
format
(
"
[%i]=
"
,
k
)
462
fastserialize
(
v
)
463
else
464
r
[
m
]
=
format
(
"
[%i]=%q,
"
,
k
,
v
)
465
end
466
end
467
else
468
m
=
m
+
1
469
if
type
(
v
)
=
=
"
table
"
then
470
r
[
m
]
=
format
(
"
[%q]=
"
,
k
)
471
fastserialize
(
v
)
472
else
473
r
[
m
]
=
format
(
"
[%q]=%q,
"
,
k
,
v
)
474
end
475
end
476
end
477
m
=
m
+
1
478
if
outer
then
479
r
[
m
]
=
"
}
"
480
else
481
r
[
m
]
=
"
},
"
482
end
483
return
r
484
end
485
return
concat
(
fastserialize
(
t
,
true
)
)
486
end
487 488
end
489 490
function
table
.
deserialize
(
str
)
491
if
not
str
or
str
=
=
"
"
then
492
return
493
end
494
local
code
=
load
(
str
)
495
if
not
code
then
496
return
497
end
498
code
=
code
(
)
499
if
not
code
then
500
return
501
end
502
return
code
503
end
504 505
-- inspect(table.fastserialize { a = 1, b = { [0]=4, { 5, 6 } }, c = { d = 7, e = 'f"g\nh' } })
506 507
function
table
.
load
(
filename
,
loader
)
508
if
filename
then
509
local
t
=
(
loader
or
io
.
loaddata
)
(
filename
)
510
if
t
and
t
~
=
"
"
then
511
local
t
=
utftoeight
(
t
)
512
t
=
load
(
t
)
513
if
type
(
t
)
=
=
"
function
"
then
514
t
=
t
(
)
515
if
type
(
t
)
=
=
"
table
"
then
516
return
t
517
end
518
end
519
end
520
end
521
end
522 523
function
table
.
save
(
filename
,
t
,
n
,
...
)
524
io
.
savedata
(
filename
,
table
.
serialize
(
t
,
n
=
=
nil
and
true
or
n
,
...
)
)
-- no frozen table.serialize
525
end
526 527
local
f_key_value
=
formatters
[
"
%s=%q
"
]
528
local
f_add_table
=
formatters
[
"
{%t},\n
"
]
529
local
f_return_table
=
formatters
[
"
return {\n%t}
"
]
530 531
local
function
slowdrop
(
t
)
-- maybe less memory (intermediate concat)
532
local
r
=
{
}
533
local
l
=
{
}
534
for
i
=
1
,
#
t
do
535
local
ti
=
t
[
i
]
536
local
j
=
0
537
for
k
,
v
in
next
,
ti
do
538
j
=
j
+
1
539
l
[
j
]
=
f_key_value
(
k
,
v
)
540
end
541
r
[
i
]
=
f_add_table
(
l
)
542
end
543
return
f_return_table
(
r
)
544
end
545 546
local
function
fastdrop
(
t
)
547
local
r
=
{
"
return {\n
"
}
548
local
m
=
1
549
for
i
=
1
,
#
t
do
550
local
ti
=
t
[
i
]
551
m
=
m
+
1
r
[
m
]
=
"
{
"
552
for
k
,
v
in
next
,
ti
do
553
m
=
m
+
1
r
[
m
]
=
f_key_value
(
k
,
v
)
554
end
555
m
=
m
+
1
r
[
m
]
=
"
},\n
"
556
end
557
m
=
m
+
1
558
r
[
m
]
=
"
}
"
559
return
concat
(
r
)
560
end
561 562
function
table
.
drop
(
t
,
slow
)
-- only { { a=2 }, {a=3} } -- for special cases
563
if
#
t
=
=
0
then
564
return
"
return { }
"
565
elseif
slow
=
=
true
then
566
return
slowdrop
(
t
)
-- less memory
567
else
568
return
fastdrop
(
t
)
-- some 15% faster
569
end
570
end
571 572
-- inspect(table.drop({ { a=2 }, {a=3} }))
573
-- inspect(table.drop({ { a=2 }, {a=3} },true))
574 575
-- function table.autokey(t,k) -- replaced
576
-- local v = { }
577
-- t[k] = v
578
-- return v
579
-- end
580 581
local
selfmapper
=
{
__index
=
function
(
t
,
k
)
t
[
k
]
=
k
return
k
end
}
582 583
function
table
.
twowaymapper
(
t
)
-- takes a 0/1 .. n indexed table and returns
584
if
not
t
then
-- it with string-numbers as indices + reverse
585
t
=
{
}
-- mapping (all strings) .. used in cvs etc but
586
else
-- typically a helper that one forgets about
587
local
zero
=
rawget
(
t
,
0
)
-- so it might move someplace else
588
for
i
=
zero
and
0
or
1
,
#
t
do
589
local
ti
=
t
[
i
]
-- t[1] = "one"
590
if
ti
then
591
local
i
=
tostring
(
i
)
592
t
[
i
]
=
ti
-- t["1"] = "one"
593
t
[
ti
]
=
i
-- t["one"] = "1"
594
end
595
end
596
end
597
-- setmetatableindex(t,"key")
598
setmetatable
(
t
,
selfmapper
)
599
return
t
600
end
601 602
-- The next version is somewhat faster, although in practice one will seldom
603
-- serialize a lot using this one. Often the above variants are more efficient.
604
-- If we would really need this a lot, we could hash q keys, or just not used
605
-- indented code.
606 607
-- char-def.lua : 0.53 -> 0.38
608
-- husayni.tma : 0.28 -> 0.19
609 610
local
f_start_key_idx
=
formatters
[
"
%w{
"
]
611
local
f_start_key_num
=
JITSUPPORTED
and
formatters
[
"
%w[%s]={
"
]
or
formatters
[
"
%w[%q]={
"
]
612
local
f_start_key_str
=
formatters
[
"
%w[%q]={
"
]
613
local
f_start_key_boo
=
formatters
[
"
%w[%l]={
"
]
614
local
f_start_key_nop
=
formatters
[
"
%w{
"
]
615 616
local
f_stop
=
formatters
[
"
%w},
"
]
617 618
local
f_key_num_value_num
=
JITSUPPORTED
and
formatters
[
"
%w[%s]=%s,
"
]
or
formatters
[
"
%w[%s]=%q,
"
]
619
local
f_key_str_value_num
=
JITSUPPORTED
and
formatters
[
"
%w[%Q]=%s,
"
]
or
formatters
[
"
%w[%Q]=%q,
"
]
620
local
f_key_boo_value_num
=
JITSUPPORTED
and
formatters
[
"
%w[%l]=%s,
"
]
or
formatters
[
"
%w[%l]=%q,
"
]
621 622
local
f_key_num_value_str
=
JITSUPPORTED
and
formatters
[
"
%w[%s]=%Q,
"
]
or
formatters
[
"
%w[%q]=%Q,
"
]
623
local
f_key_str_value_str
=
formatters
[
"
%w[%Q]=%Q,
"
]
624
local
f_key_boo_value_str
=
formatters
[
"
%w[%l]=%Q,
"
]
625 626
local
f_key_num_value_boo
=
JITSUPPORTED
and
formatters
[
"
%w[%s]=%l,
"
]
or
formatters
[
"
%w[%q]=%l,
"
]
627
local
f_key_str_value_boo
=
formatters
[
"
%w[%Q]=%l,
"
]
628
local
f_key_boo_value_boo
=
formatters
[
"
%w[%l]=%l,
"
]
629 630
local
f_key_num_value_not
=
JITSUPPORTED
and
formatters
[
"
%w[%s]={},
"
]
or
formatters
[
"
%w[%q]={},
"
]
631
local
f_key_str_value_not
=
formatters
[
"
%w[%Q]={},
"
]
632
local
f_key_boo_value_not
=
formatters
[
"
%w[%l]={},
"
]
633 634
local
f_key_num_value_seq
=
JITSUPPORTED
and
formatters
[
"
%w[%s]={ %, t },
"
]
or
formatters
[
"
%w[%q]={ %, t },
"
]
635
local
f_key_str_value_seq
=
formatters
[
"
%w[%Q]={ %, t },
"
]
636
local
f_key_boo_value_seq
=
formatters
[
"
%w[%l]={ %, t },
"
]
637 638
local
f_val_num
=
JITSUPPORTED
and
formatters
[
"
%w%s,
"
]
or
formatters
[
"
%w%q,
"
]
639
local
f_val_str
=
formatters
[
"
%w%Q,
"
]
640
local
f_val_boo
=
formatters
[
"
%w%l,
"
]
641
local
f_val_not
=
formatters
[
"
%w{},
"
]
642
local
f_val_seq
=
formatters
[
"
%w{ %, t },
"
]
643
local
f_fin_seq
=
formatters
[
"
%, t }
"
]
644 645
local
f_table_return
=
formatters
[
"
return {
"
]
646
local
f_table_name
=
formatters
[
"
%s={
"
]
647
local
f_table_direct
=
formatters
[
"
{
"
]
648
local
f_table_entry
=
formatters
[
"
[%Q]={
"
]
649
local
f_table_finish
=
formatters
[
"
}
"
]
650 651
local
spaces
=
utilities
.
strings
.
newrepeater
(
"
"
)
652 653
local
original_serialize
=
table
.
serialize
-- the extensive one, the one we started with
654 655
-- there is still room for optimization: index run, key run, but i need to check with the
656
-- latest lua for the value of #n (with holes) .. anyway for tracing purposes we want
657
-- indices / keys being sorted, so it will never be real fast
658 659
local
is_simple_table
=
table
.
is_simple_table
660 661
-- local function is_simple_table(t)
662
-- local nt = #t
663
-- if nt > 0 then
664
-- local n = 0
665
-- for _, v in next, t do
666
-- n = n + 1
667
-- if type(v) == "table" then
668
-- return nil
669
-- end
670
-- end
671
-- -- local haszero = t[0]
672
-- local haszero = rawget(t,0) -- don't trigger meta
673
-- if n == nt then
674
-- local tt = { }
675
-- for i=1,nt do
676
-- local v = t[i]
677
-- local tv = type(v)
678
-- if tv == "number" then
679
-- tt[i] = v -- not needed tostring(v)
680
-- elseif tv == "string" then
681
-- tt[i] = format("%q",v) -- f_string(v)
682
-- elseif tv == "boolean" then
683
-- tt[i] = v and "true" or "false"
684
-- else
685
-- return nil
686
-- end
687
-- end
688
-- return tt
689
-- elseif haszero and (n == nt + 1) then
690
-- local tt = { }
691
-- for i=0,nt do
692
-- local v = t[i]
693
-- local tv = type(v)
694
-- if tv == "number" then
695
-- tt[i+1] = v -- not needed tostring(v)
696
-- elseif tv == "string" then
697
-- tt[i+1] = format("%q",v) -- f_string(v)
698
-- elseif tv == "boolean" then
699
-- tt[i+1] = v and "true" or "false"
700
-- else
701
-- return nil
702
-- end
703
-- end
704
-- tt[1] = "[0] = " .. tt[1]
705
-- return tt
706
-- end
707
-- end
708
-- return nil
709
-- end
710 711
-- In order to overcome the luajit (65K constant) limitation I tried a split approach,
712
-- i.e. outputting the first level tables as locals but that failed with large cjk
713
-- fonts too so I removed that ... just use luatex instead.
714 715
local
function
serialize
(
root
,
name
,
specification
)
716 717
if
type
(
specification
)
=
=
"
table
"
then
718
return
original_serialize
(
root
,
name
,
specification
)
-- the original one
719
end
720 721
local
t
-- = { }
722
local
n
=
1
723
local
unknown
=
false
724 725
local
function
do_serialize
(
root
,
name
,
depth
,
level
,
indexed
)
726
if
level
>
0
then
727
n
=
n
+
1
728
if
indexed
then
729
t
[
n
]
=
f_start_key_idx
(
depth
)
730
else
731
local
tn
=
type
(
name
)
732
if
tn
=
=
"
number
"
then
733
t
[
n
]
=
f_start_key_num
(
depth
,
name
)
734
elseif
tn
=
=
"
string
"
then
735
t
[
n
]
=
f_start_key_str
(
depth
,
name
)
736
elseif
tn
=
=
"
boolean
"
then
737
t
[
n
]
=
f_start_key_boo
(
depth
,
name
)
738
else
739
t
[
n
]
=
f_start_key_nop
(
depth
)
740
end
741
end
742
depth
=
depth
+
1
743
end
744
-- we could check for k (index) being number (cardinal)
745
if
root
and
next
(
root
)
~
=
nil
then
746
local
first
=
nil
747
local
last
=
#
root
748
if
last
>
0
then
749
for
k
=
1
,
last
do
750
if
rawget
(
root
,
k
)
=
=
nil
then
751
-- if root[k] == nil then
752
last
=
k
-
1
753
break
754
end
755
end
756
if
last
>
0
then
757
first
=
1
758
end
759
end
760
local
sk
=
sortedkeys
(
root
)
761
for
i
=
1
,
#
sk
do
762
local
k
=
sk
[
i
]
763
local
v
=
root
[
k
]
764
local
tv
=
type
(
v
)
765
local
tk
=
type
(
k
)
766
if
first
and
tk
=
=
"
number
"
and
k
<
=
last
and
k
>
=
first
then
767
if
tv
=
=
"
number
"
then
768
n
=
n
+
1
t
[
n
]
=
f_val_num
(
depth
,
v
)
769
elseif
tv
=
=
"
string
"
then
770
n
=
n
+
1
t
[
n
]
=
f_val_str
(
depth
,
v
)
771
elseif
tv
=
=
"
table
"
then
772
if
next
(
v
)
=
=
nil
then
-- tricky as next is unpredictable in a hash
773
n
=
n
+
1
t
[
n
]
=
f_val_not
(
depth
)
774
else
775
local
st
=
is_simple_table
(
v
)
776
if
st
then
777
n
=
n
+
1
t
[
n
]
=
f_val_seq
(
depth
,
st
)
778
else
779
do_serialize
(
v
,
k
,
depth
,
level
+
1
,
true
)
780
end
781
end
782
elseif
tv
=
=
"
boolean
"
then
783
n
=
n
+
1
t
[
n
]
=
f_val_boo
(
depth
,
v
)
784
elseif
unknown
then
785
n
=
n
+
1
t
[
n
]
=
f_val_str
(
depth
,
tostring
(
v
)
)
786
end
787
elseif
tv
=
=
"
number
"
then
788
if
tk
=
=
"
number
"
then
789
n
=
n
+
1
t
[
n
]
=
f_key_num_value_num
(
depth
,
k
,
v
)
790
elseif
tk
=
=
"
string
"
then
791
n
=
n
+
1
t
[
n
]
=
f_key_str_value_num
(
depth
,
k
,
v
)
792
elseif
tk
=
=
"
boolean
"
then
793
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_num
(
depth
,
k
,
v
)
794
elseif
unknown
then
795
n
=
n
+
1
t
[
n
]
=
f_key_str_value_num
(
depth
,
tostring
(
k
)
,
v
)
796
end
797
elseif
tv
=
=
"
string
"
then
798
if
tk
=
=
"
number
"
then
799
n
=
n
+
1
t
[
n
]
=
f_key_num_value_str
(
depth
,
k
,
v
)
800
elseif
tk
=
=
"
string
"
then
801
n
=
n
+
1
t
[
n
]
=
f_key_str_value_str
(
depth
,
k
,
v
)
802
elseif
tk
=
=
"
boolean
"
then
803
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_str
(
depth
,
k
,
v
)
804
elseif
unknown
then
805
n
=
n
+
1
t
[
n
]
=
f_key_str_value_str
(
depth
,
tostring
(
k
)
,
v
)
806
end
807
elseif
tv
=
=
"
table
"
then
808
if
next
(
v
)
=
=
nil
then
809
if
tk
=
=
"
number
"
then
810
n
=
n
+
1
t
[
n
]
=
f_key_num_value_not
(
depth
,
k
)
811
elseif
tk
=
=
"
string
"
then
812
n
=
n
+
1
t
[
n
]
=
f_key_str_value_not
(
depth
,
k
)
813
elseif
tk
=
=
"
boolean
"
then
814
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_not
(
depth
,
k
)
815
elseif
unknown
then
816
n
=
n
+
1
t
[
n
]
=
f_key_str_value_not
(
depth
,
tostring
(
k
)
)
817
end
818
else
819
local
st
=
is_simple_table
(
v
)
820
if
not
st
then
821
do_serialize
(
v
,
k
,
depth
,
level
+
1
)
822
elseif
tk
=
=
"
number
"
then
823
n
=
n
+
1
t
[
n
]
=
f_key_num_value_seq
(
depth
,
k
,
st
)
824
elseif
tk
=
=
"
string
"
then
825
n
=
n
+
1
t
[
n
]
=
f_key_str_value_seq
(
depth
,
k
,
st
)
826
elseif
tk
=
=
"
boolean
"
then
827
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_seq
(
depth
,
k
,
st
)
828
elseif
unknown
then
829
n
=
n
+
1
t
[
n
]
=
f_key_str_value_seq
(
depth
,
tostring
(
k
)
,
st
)
830
end
831
end
832
elseif
tv
=
=
"
boolean
"
then
833
if
tk
=
=
"
number
"
then
834
n
=
n
+
1
t
[
n
]
=
f_key_num_value_boo
(
depth
,
k
,
v
)
835
elseif
tk
=
=
"
string
"
then
836
n
=
n
+
1
t
[
n
]
=
f_key_str_value_boo
(
depth
,
k
,
v
)
837
elseif
tk
=
=
"
boolean
"
then
838
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_boo
(
depth
,
k
,
v
)
839
elseif
unknown
then
840
n
=
n
+
1
t
[
n
]
=
f_key_str_value_boo
(
depth
,
tostring
(
k
)
,
v
)
841
end
842
else
843
if
tk
=
=
"
number
"
then
844
n
=
n
+
1
t
[
n
]
=
f_key_num_value_str
(
depth
,
k
,
tostring
(
v
)
)
845
elseif
tk
=
=
"
string
"
then
846
n
=
n
+
1
t
[
n
]
=
f_key_str_value_str
(
depth
,
k
,
tostring
(
v
)
)
847
elseif
tk
=
=
"
boolean
"
then
848
n
=
n
+
1
t
[
n
]
=
f_key_boo_value_str
(
depth
,
k
,
tostring
(
v
)
)
849
elseif
unknown
then
850
n
=
n
+
1
t
[
n
]
=
f_key_str_value_str
(
depth
,
tostring
(
k
)
,
tostring
(
v
)
)
851
end
852
end
853
end
854
end
855
if
level
>
0
then
856
n
=
n
+
1
t
[
n
]
=
f_stop
(
depth
-1
)
857
end
858
end
859 860
local
tname
=
type
(
name
)
861 862
if
tname
=
=
"
string
"
then
863
if
name
=
=
"
return
"
then
864
t
=
{
f_table_return
(
)
}
865
else
866
t
=
{
f_table_name
(
name
)
}
867
end
868
elseif
tname
=
=
"
number
"
then
869
t
=
{
f_table_entry
(
name
)
}
870
elseif
tname
=
=
"
boolean
"
then
871
if
name
then
872
t
=
{
f_table_return
(
)
}
873
else
874
t
=
{
f_table_direct
(
)
}
875
end
876
else
877
t
=
{
f_table_name
(
"
t
"
)
}
878
end
879 880
if
root
then
881
-- The dummy access will initialize a table that has a delayed initialization
882
-- using a metatable. (maybe explicitly test for metatable). This can crash on
883
-- metatables that check the index against a number.
884
if
getmetatable
(
root
)
then
-- todo: make this an option, maybe even per subtable
885
local
dummy
=
root
.
_w_h_a_t_e_v_e_r_
-- needed
886
root
.
_w_h_a_t_e_v_e_r_
=
nil
887
end
888
-- Let's forget about empty tables.
889
if
next
(
root
)
~
=
nil
then
890
local
st
=
is_simple_table
(
root
)
891
if
st
then
892
return
t
[
1
]
.
.
f_fin_seq
(
st
)
-- todo: move up and in one go
893
else
894
do_serialize
(
root
,
name
,
1
,
0
)
895
end
896
end
897
end
898
n
=
n
+
1
899
t
[
n
]
=
f_table_finish
(
)
900
return
concat
(
t
,
"
\n
"
)
901
end
902 903
table
.
serialize
=
serialize
904 905
if
setinspector
then
906
setinspector
(
"
table
"
,
function
(
v
)
907
if
type
(
v
)
=
=
"
table
"
then
908
print
(
serialize
(
v
,
"
table
"
,
{
metacheck
=
false
}
)
)
909
return
true
910
end
911
end
)
912
end
913 914
-- ordered hashes (for now here but in the table namespace):
915 916
-- local t = table.orderedhash()
917
--
918
-- t["1"] = { "a", "b" }
919
-- t["2"] = { }
920
-- t["2a"] = { "a", "c", "d" }
921
--
922
-- for k, v in table.ordered(t) do
923
-- ...
924
-- end
925 926
local
mt
=
{
927
__newindex
=
function
(
t
,
k
,
v
)
928
local
n
=
t
.
last
+
1
929
t
.
last
=
n
930
t
.
list
[
n
]
=
k
931
t
.
hash
[
k
]
=
v
932
end
,
933
__index
=
function
(
t
,
k
)
934
return
t
.
hash
[
k
]
935
end
,
936
__len
=
function
(
t
)
937
return
t
.
last
938
end
,
939
}
940 941
function
table
.
orderedhash
(
)
942
return
setmetatable
(
{
list
=
{
}
,
hash
=
{
}
,
last
=
0
}
,
mt
)
943
end
944 945
function
table
.
ordered
(
t
)
946
local
n
=
t
.
last
947
if
n
>
0
then
948
local
l
=
t
.
list
949
local
i
=
1
950
local
h
=
t
.
hash
951
local
f
=
function
(
)
952
if
i
<
=
n
then
953
local
k
=
i
954
local
v
=
h
[
l
[
k
]
]
955
i
=
i
+
1
956
return
k
,
v
957
end
958
end
959
return
f
,
1
,
h
[
l
[
1
]
]
960
else
961
return
function
(
)
end
962
end
963
end
964 965
-- function table.randomremove(t,n)
966
-- if not n then
967
-- n = #t
968
-- end
969
-- if n > 0 then
970
-- return remove(t,random(1,n))
971
-- end
972
-- end
973