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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
|
--[[ vON 1.3.4
Copyright 2012-2014 Alexandru-Mihai Maftei
aka Vercas
GitHub Repository:
https://github.com/vercas/vON
You may use this for any purpose as long as:
- You don't remove this copyright notice.
- You don't claim this to be your own.
- You properly credit the author (Vercas) if you publish your work based on (and/or using) this.
If you modify the code for any purpose, the above obligations still apply.
If you make any interesting modifications, try forking the GitHub repository instead.
Instead of copying this code over for sharing, rather use the link:
https://github.com/vercas/vON/blob/master/von.lua
The author may not be held responsible for any damage or losses directly or indirectly caused by
the use of vON.
If you disagree with the above, don't use the code.
-----------------------------------------------------------------------------------------------------------------------------
Thanks to the following people for their contribution:
- Divran Suggested improvements for making the code quicker.
Suggested an excellent new way of deserializing strings.
Lead me to finding an extreme flaw in string parsing.
- pennerlord Provided some performance tests to help me improve the code.
- Chessnut Reported bug with handling of nil values when deserializing array components.
- People who contributed on the GitHub repository by reporting bugs, posting fixes, etc.
-----------------------------------------------------------------------------------------------------------------------------
The vanilla types supported in this release of vON are:
- table
- number
- boolean
- string
- nil
The Garry's Mod-specific types supported in this release are:
- Vector
- Angle
+ Entities:
- Entity
- Vehicle
- Weapon
- NPC
- Player
- NextBot
These are the types one would normally serialize.
-----------------------------------------------------------------------------------------------------------------------------
New in this version:
- Fixed addition of extra entity types. I messed up really badly.
--]]
local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable
local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next
--[[ This section contains localized functions which (de)serialize
variables according to the types found. ]]
-- This is kept away from the table for speed.
function d_findVariable(s, i, len, lastType, jobstate)
local i, c, typeRead, val = i or 1
-- Keep looping through the string.
while true do
-- Stop at the end. Throw an error. This function MUST NOT meet the end!
if i > len then
error("vON: Reached end of string, cannot form proper variable.")
end
-- Cache the character. Nobody wants to look for the same character ten times.
c = sub(s, i, i)
-- If it just read a type definition, then a variable HAS to come after it.
if typeRead then
-- Attempt to deserialize a variable of the freshly read type.
val, i = _deserialize[lastType](s, i, len, false, jobstate)
-- Return the value read, the index of the last processed character, and the type of the last read variable.
return val, i, lastType
-- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store.
elseif c == "@" then
return nil, i, lastType
-- $ means a table reference will follow - a number basically.
elseif c == "$" then
lastType = "table_reference"
typeRead = true
-- n means a number will follow. Base 10... :C
elseif c == "n" then
lastType = "number"
typeRead = true
-- b means boolean flags.
elseif c == "b" then
lastType = "boolean"
typeRead = true
-- ' means the start of a string.
elseif c == "'" then
lastType = "string"
typeRead = true
-- " means the start of a string prior to version 1.2.0.
elseif c == "\"" then
lastType = "oldstring"
typeRead = true
-- { means the start of a table!
elseif c == "{" then
lastType = "table"
typeRead = true
--[[ Garry's Mod types go here ]]
-- e means an entity ID will follow.
elseif c == "e" then
lastType = "Entity"
typeRead = true
--[[
-- c means a vehicle ID will follow.
elseif c == "c" then
lastType = "Vehicle"
typeRead = true
-- w means a weapon entity ID will follow.
elseif c == "w" then
lastType = "Weapon"
typeRead = true
-- x means a NPC ID will follow.
elseif c == "x" then
lastType = "NPC"
typeRead = true
--]]
-- p means a player ID will follow.
-- Kept for backwards compatibility.
elseif c == "p" then
lastType = "Entity"
typeRead = true
-- v means a vector will follow. 3 numbers.
elseif c == "v" then
lastType = "Vector"
typeRead = true
-- a means an Euler angle will follow. 3 numbers.
elseif c == "a" then
lastType = "Angle"
typeRead = true
--[[ Garry's Mod types end here ]]
-- If no type has been found, attempt to deserialize the last type read.
elseif lastType then
val, i = _deserialize[lastType](s, i, len, false, jobstate)
return val, i, lastType
-- This will occur if the very first character in the vON code is wrong.
else
error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c)
end
-- Move the pointer one step forward.
i = i + 1
end
end
-- This is kept away from the table for speed.
-- Yeah, ton of parameters.
function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate)
local tp = type(data)
if jobstate[1] and jobstate[2][data] then
tp = "table_reference"
end
-- Basically, if the type changes.
if lastType ~= tp then
-- Remember the new type. Caching the type is useless.
lastType = tp
if _serialize[lastType] then
-- Return the serialized data and the (new) last type.
-- The second argument, which is true now, means that the data type was just changed.
return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType
else
error("vON: No serializer defined for type \"" .. lastType .. "\"!")
end
end
-- Otherwise, simply serialize the data.
return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType
end
--[[ This section contains the tables with the functions necessary
for decoding basic Lua data types. ]]
_deserialize = {
-- Well, tables are very loose...
-- The first table doesn't have to begin and end with { and }.
["table"] = function(s, i, len, unnecessaryEnd, jobstate)
local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1
-- Locals, locals, locals, locals, locals, locals, locals, locals and locals.
if sub(s, i, i) == "#" then
local e = find(s, "#", i + 2, true)
if e then
local id = tonumber(sub(s, i + 1, e - 1))
if id then
if jobstate[1][id] and not jobstate[2] then
error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?")
end
jobstate[1][id] = ret
i = e + 1
else
error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!")
end
else
error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!")
end
end
-- Keep looping.
while true do
-- Until it meets the end.
if i > len then
-- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example.
if unnecessaryEnd then
return ret, i
-- Otherwise, the data has to be damaged.
else
error("vON: Reached end of string, incomplete table definition.")
end
end
-- Cache the character.
c = sub(s, i, i)
--print(i, "table char:", c, tostring(unnecessaryEnd))
-- If it's the end of a table definition, return.
if c == "}" then
return ret, i
-- If it's the component separator, switch to key:value pairs.
elseif c == "~" then
numeric = false
elseif c == ";" then
-- Lol, nothing!
-- Remenant from numbers, for faster parsing.
-- OK, now, if it's on the numeric component, simply add everything encountered.
elseif numeric then
-- Find a variable and it's value
val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
-- Add it to the table.
ret[ind] = val
ind = ind + 1
-- Otherwise, if it's the key:value component...
else
-- If a value is expected...
if expectValue then
-- Read it.
val, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
-- Add it?
ret[key] = val
-- Clean up.
expectValue, key = false, nil
-- If it's the separator...
elseif c == ":" then
-- Expect a value next.
expectValue = true
-- But, if there's a key read already...
elseif key then
-- Then this is malformed.
error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c)
-- Otherwise the key will be read.
else
-- I love multi-return and multi-assignement.
key, i, lastType = d_findVariable(s, i, len, lastType, jobstate)
end
end
i = i + 1
end
return nil, i
end,
-- Just a number which points to a table.
["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
local n = tonumber(sub(s, i, a - 1))
if n then
return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1
else
error("vON: Table reference definition does not contain a valid number!")
end
end
-- Using %D breaks identification of negative numbers. :(
error("vON: Number definition started... Found no end.")
end,
-- Numbers are weakly defined.
-- The declaration is not very explicit. It'll do it's best to parse the number.
-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards.
["number"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1
end
-- Using %D breaks identification of negative numbers. :(
error("vON: Number definition started... Found no end.")
end,
-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false.
-- Any other attempt at boolean declaration will result in a failure.
["boolean"] = function(s, i, len, unnecessaryEnd, jobstate)
local c = sub(s,i,i)
-- Only one character is needed.
-- If it's 1, then it's true
if c == "1" then
return true, i
-- If it's 0, then it's false.
elseif c == "0" then
return false, i
end
-- Any other supposely "boolean" is just a sign of malformed data.
error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c)
end,
-- Strings prior to 1.2.0
["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate)
local res, i, a = "", i or 1
-- Locals, locals, locals, locals
while true do
a = find(s, "\"", i, true)
if a then
if sub(s, a - 1, a - 1) == "\\" then
res = res .. sub(s, i, a - 2) .. "\""
i = a + 1
else
return res .. sub(s, i, a - 2), a
end
else
error("vON: Old string definition started... Found no end.")
end
end
end,
-- Strings after 1.2.0
["string"] = function(s, i, len, unnecessaryEnd, jobstate)
local res, i, a = "", i or 1
-- Locals, locals, locals, locals
while true do
a = find(s, "\"", i, true)
if a then
if sub(s, a - 1, a - 1) == "\\" then
res = res .. sub(s, i, a - 2) .. "\""
i = a + 1
else
return res .. sub(s, i, a - 1), a
end
else
error("vON: String definition started... Found no end.")
end
end
end,
}
_serialize = {
-- Uh. Nothing to comment.
-- Ton of parameters.
-- Makes stuff faster than simply passing it around in locals.
-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY.
["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
--print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first)))
local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0
-- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals.
-- First thing to be done is separate the numeric and key:value components of the given table in two tables.
-- pairs(data) is slower than next, data as far as my tests tell me.
for k, v in next, data do
-- Skip the numeric keyz.
if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks,
keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer.
end -- k % 1 ~= 0 is the fastest way to check if a number
end -- is NOT an integer. > is proven slower.
keyvalsLen = #keyvals
-- Main chunk - no initial character.
if not first then
result[#result + 1] = "{"
end
if jobstate[1] and jobstate[1][data] then
if jobstate[2][data] then
error("vON: Table #" .. jobstate[1][data] .. " written twice..?")
end
result[#result + 1] = "#"
result[#result + 1] = jobstate[1][data]
result[#result + 1] = "#"
jobstate[2][data] = true
end
-- Add numeric values.
if len > 0 then
for i = 1, len do
val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate)
result[#result + 1] = val
end
end
-- If there are key:value pairs.
if keyvalsLen > 0 then
-- Insert delimiter.
result[#result + 1] = "~"
-- Insert key:value pairs.
for _i = 1, keyvalsLen do
keyvalsProgress = keyvalsProgress + 1
val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate)
result[#result + 1] = val..":"
val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate)
result[#result + 1] = val
end
end
-- Main chunk needs no ending character.
if not first then
result[#result + 1] = "}"
end
return concat(result)
end,
-- Number which points to table.
["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
data = jobstate[1][data]
-- If a number hasn't been written before, add the type prefix.
if mustInitiate then
if isKey or isLast then
return "$"..data
else
return "$"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- Normal concatenations is a lot faster with small strings than table.concat
-- Also, not so branched-ish.
["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
-- If a number hasn't been written before, add the type prefix.
if mustInitiate then
if isKey or isLast then
return "n"..data
else
return "n"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- I hope gsub is fast enough.
["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best.
return "\"" .. gsub(data, "\"", "\\\"") .. "v\""
end
return "'" .. gsub(data, "\"", "\\\"") .. "\""
end,
-- Fastest.
["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
-- Prefix if we must.
if mustInitiate then
if data then
return "b1"
else
return "b0"
end
end
if data then
return "1"
else
return "0"
end
end,
-- Fastest.
["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
return "@"
end,
}
--[[ This section handles additions necessary for Garry's Mod. ]]
if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod.
local Entity = Entity
local extra_deserialize = {
-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway.
-- Exactly like a number definition, except it begins with "e".
["Entity"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a = i or 1
-- Locals, locals, locals, locals
a = find(s, "[;:}~]", i)
if a then
return Entity(tonumber(sub(s, i, a - 1))), a - 1
end
error("vON: Entity ID definition started... Found no end.")
end,
-- A pair of 3 numbers separated by a comma (,).
["Vector"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a, x, y, z = i or 1
-- Locals, locals, locals, locals
a = find(s, ",", i)
if a then
x = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, ",", i)
if a then
y = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, "[;:}~]", i)
if a then
z = tonumber(sub(s, i, a - 1))
end
if x and y and z then
return Vector(x, y, z), a - 1
end
error("vON: Vector definition started... Found no end.")
end,
-- A pair of 3 numbers separated by a comma (,).
["Angle"] = function(s, i, len, unnecessaryEnd, jobstate)
local i, a, p, y, r = i or 1
-- Locals, locals, locals, locals
a = find(s, ",", i)
if a then
p = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, ",", i)
if a then
y = tonumber(sub(s, i, a - 1))
i = a + 1
end
a = find(s, "[;:}~]", i)
if a then
r = tonumber(sub(s, i, a - 1))
end
if p and y and r then
return Angle(p, y, r), a - 1
end
error("vON: Angle definition started... Found no end.")
end,
}
local extra_serialize = {
-- Same as numbers, except they start with "e" instead of "n".
["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
data = data:EntIndex()
if mustInitiate then
if isKey or isLast then
return "e"..data
else
return "e"..data..";"
end
end
if isKey or isLast then
return data
else
return data..";"
end
end,
-- 3 numbers separated by a comma.
["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if mustInitiate then
if isKey or isLast then
return "v"..data.x..","..data.y..","..data.z
else
return "v"..data.x..","..data.y..","..data.z..";"
end
end
if isKey or isLast then
return data.x..","..data.y..","..data.z
else
return data.x..","..data.y..","..data.z..";"
end
end,
-- 3 numbers separated by a comma.
["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate)
if mustInitiate then
if isKey or isLast then
return "a"..data.p..","..data.y..","..data.r
else
return "a"..data.p..","..data.y..","..data.r..";"
end
end
if isKey or isLast then
return data.p..","..data.y..","..data.r
else
return data.p..","..data.y..","..data.r..";"
end
end,
}
for k, v in pairs(extra_serialize) do
_serialize[k] = v
end
for k, v in pairs(extra_deserialize) do
_deserialize[k] = v
end
local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" }
for i = 1, #extraEntityTypes do
_serialize[extraEntityTypes[i]] = _serialize.Entity
end
end
--[[ This section exposes the functions of the library. ]]
local function checkTableForRecursion(tab, checked, assoc)
local id = checked.ID
if not checked[tab] and not assoc[tab] then
assoc[tab] = id
checked.ID = id + 1
else
checked[tab] = true
end
for k, v in pairs(tab) do
if type(k) == "table" and not checked[k] then
checkTableForRecursion(k, checked, assoc)
end
if type(v) == "table" and not checked[v] then
checkTableForRecursion(v, checked, assoc)
end
end
end
local _s_table = _serialize.table
local _d_table = _deserialize.table
_d_meta = {
__call = function(self, str, allowIdRewriting)
if type(str) == "string" then
return _d_table(str, nil, #str, true, {{}, allowIdRewriting})
end
error("vON: You must deserialize a string, not a "..type(str))
end
}
_s_meta = {
__call = function(self, data, checkRecursion)
if type(data) == "table" then
if checkRecursion then
local assoc, checked = {}, {ID = 1}
checkTableForRecursion(data, checked, assoc)
return _s_table(data, nil, nil, nil, nil, true, {assoc, {}})
end
return _s_table(data, nil, nil, nil, nil, true, {false})
end
error("vON: You must serialize a table, not a "..type(data))
end
}
von = {
version = "1.3.4",
versionNumber = 1003004, -- Reserving 3 digits per version component.
deserialize = setmetatable(_deserialize,_d_meta),
serialize = setmetatable(_serialize,_s_meta)
}
return von
|