ig-serialize 1.0.1 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/package.json +1 -1
- package/serialize.js +121 -28
- package/test.js +91 -13
- package/serialize2.js +0 -645
package/README.md
CHANGED
|
@@ -11,5 +11,71 @@ This extends the default JSON serialization adding the following:
|
|
|
11
11
|
|
|
12
12
|
## Motivation
|
|
13
13
|
|
|
14
|
+
This was originally built as a companion to a testing module for a
|
|
15
|
+
programming class, illustrating several concepts, including: guaranteed
|
|
16
|
+
clean isolation of data structures via serialization, instrumenting code
|
|
17
|
+
and tooling design, basic parsing, among others.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Introduction
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Serializing functions
|
|
28
|
+
|
|
29
|
+
Due to how JavaScript is designed it is not possible to trivially and
|
|
30
|
+
fully clone a function with all of it's references, `.serilaize(..)` will
|
|
31
|
+
not attempt to clone any state a function may have, this will lead to
|
|
32
|
+
loosing:
|
|
33
|
+
|
|
34
|
+
- Function closure
|
|
35
|
+
- Attributes set on the function or any of it's prototypes, including the
|
|
36
|
+
`.__proto__` value if it was changed.
|
|
37
|
+
|
|
38
|
+
Thus, care must be taken when serializing structures containing function.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### `serialize(..)` / `eJSON.stringify(..)`
|
|
44
|
+
|
|
45
|
+
### `deserialize(..)` / 'eJSON.parse(..)'
|
|
46
|
+
|
|
47
|
+
### `deepCopy(..)`
|
|
48
|
+
|
|
49
|
+
### `partialDeepCopy(..)`
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Format
|
|
53
|
+
|
|
54
|
+
The output of `.serialize(..)` is a strict superset of [standard JSON](https://www.json.org/json-en.html),
|
|
55
|
+
while the input format is a bit more relaxed than in several details.
|
|
56
|
+
|
|
57
|
+
Extensions to JSON:
|
|
58
|
+
- Recursion
|
|
59
|
+
- undefined / NaN
|
|
60
|
+
- BigInt
|
|
61
|
+
- Map / Set
|
|
62
|
+
- Function
|
|
63
|
+
|
|
64
|
+
### Structural paths
|
|
65
|
+
|
|
66
|
+
### Recursion
|
|
67
|
+
|
|
68
|
+
### null types
|
|
69
|
+
|
|
70
|
+
### BigInt
|
|
71
|
+
|
|
72
|
+
### Map / Set
|
|
73
|
+
|
|
74
|
+
### Functions
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
14
80
|
|
|
15
81
|
|
package/package.json
CHANGED
package/serialize.js
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
/**********************************************************************
|
|
2
2
|
*
|
|
3
|
+
* TODO would be useful to split this into:
|
|
4
|
+
* - object traversal using object path
|
|
5
|
+
* Object.walk(..) -- a-la Python's walk(..)
|
|
6
|
+
* Object.graph(..)
|
|
7
|
+
* like .map(..) but deep and paths instead of indexes...
|
|
8
|
+
* - get/set object by path
|
|
9
|
+
* Object.get(..) / Object.set(..)
|
|
10
|
+
*
|
|
11
|
+
* XXX the current path implementation is fully complient to the task at
|
|
12
|
+
* hand but it will not suite the diff task as there is no way to know
|
|
13
|
+
* the meaning of the path element without seeing the object (map/set
|
|
14
|
+
* indexes)...
|
|
15
|
+
* a different path format would suit both tasks and make things more
|
|
16
|
+
* universal:
|
|
17
|
+
* Current:
|
|
18
|
+
* [1, 'a', 5, 0, 3]
|
|
19
|
+
* \ \ +-++ +---------------- Set index (from context)
|
|
20
|
+
* \ \ +------------------- Map index (from context)
|
|
21
|
+
* \ +------------------------ Object key (string)
|
|
22
|
+
* +--------------------------- Array index (from context)
|
|
23
|
+
* Proposed:
|
|
24
|
+
* [1, 'a', ['map', 5, 0], ['set', 3]]
|
|
25
|
+
* \ \ +---------+-+ +------+-+
|
|
26
|
+
* \ \ \ +--- Set index
|
|
27
|
+
* \ \ +-------------- Map index
|
|
28
|
+
* \ +--------------------------- Object key (string)
|
|
29
|
+
* +------------------------------ Array index (number)
|
|
30
|
+
* And/or:
|
|
31
|
+
* [1, ['object', 3], ['map', 5, 0], ['set', 3]]
|
|
32
|
+
* +---------+-+
|
|
33
|
+
* +----- Object attr index
|
|
34
|
+
* The questions is which to use as default for objects, the key or
|
|
35
|
+
* position?
|
|
36
|
+
* (XXX move this note to diff.jx)
|
|
37
|
+
*
|
|
38
|
+
* XXX do a revision of the JSON standard for things I could have forgotten...
|
|
3
39
|
*
|
|
4
40
|
*
|
|
5
41
|
**********************************************************************/
|
|
@@ -41,8 +77,12 @@ var debug = {
|
|
|
41
77
|
|
|
42
78
|
//---------------------------------------------------------------------
|
|
43
79
|
|
|
80
|
+
module.STRING_LENGTH_REF = RECURSIVE.length * 8
|
|
81
|
+
|
|
82
|
+
|
|
44
83
|
//
|
|
45
|
-
// serialize(obj[,
|
|
84
|
+
// serialize(obj[, options])
|
|
85
|
+
// serialize(obj[, indent[, depth[, options]]])
|
|
46
86
|
// -> str
|
|
47
87
|
//
|
|
48
88
|
// indent can be:
|
|
@@ -50,7 +90,7 @@ var debug = {
|
|
|
50
90
|
// string - string to use for indenting
|
|
51
91
|
//
|
|
52
92
|
//
|
|
53
|
-
// _serialize(obj, base_path, seen, indent, depth,
|
|
93
|
+
// _serialize(obj, base_path, seen, indent, depth, options)
|
|
54
94
|
// -> str
|
|
55
95
|
//
|
|
56
96
|
//
|
|
@@ -98,12 +138,16 @@ var debug = {
|
|
|
98
138
|
// ]
|
|
99
139
|
//
|
|
100
140
|
//
|
|
101
|
-
// XXX BUG
|
|
141
|
+
// XXX BUG?: using non-whitespace as indent breaks the depth of the first
|
|
102
142
|
// or last elements in sequences
|
|
103
143
|
// ...breaks .trim*() in Map/Set/Object...
|
|
104
144
|
var _serialize =
|
|
105
145
|
module._serialize =
|
|
106
|
-
function(obj, path=[], seen=new Map(), indent, depth=0,
|
|
146
|
+
function(obj, path=[], seen=new Map(), indent, depth=0, options={}){
|
|
147
|
+
var string_length_ref =
|
|
148
|
+
options.string_length_ref
|
|
149
|
+
?? module.STRING_LENGTH_REF
|
|
150
|
+
|
|
107
151
|
// recursive...
|
|
108
152
|
var p = seen.get(obj)
|
|
109
153
|
if(p != null){
|
|
@@ -117,14 +161,25 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
117
161
|
seen.set(obj, path)
|
|
118
162
|
// if functions array is given add function to it and store its
|
|
119
163
|
// index in the serialized data...
|
|
120
|
-
if(functions instanceof Array){
|
|
121
|
-
functions.push(obj)
|
|
122
|
-
obj = functions.length-1 }
|
|
164
|
+
if(options.functions instanceof Array){
|
|
165
|
+
options.functions.push(obj)
|
|
166
|
+
obj = options.functions.length-1 }
|
|
123
167
|
var s = '('+ obj.toString() +')'
|
|
124
168
|
return FUNCTION
|
|
125
169
|
.replace('%', s.length +','+ s) }
|
|
126
170
|
|
|
171
|
+
// long strings...
|
|
172
|
+
// NOTE: this saves on output size...
|
|
173
|
+
if(typeof(obj) == 'string'
|
|
174
|
+
&& obj.length > string_length_ref){
|
|
175
|
+
seen.set(obj, path) }
|
|
176
|
+
// BigInt...
|
|
177
|
+
if(typeof(obj) == 'bigint'){
|
|
178
|
+
seen.set(obj, path)
|
|
179
|
+
return obj.toString() +'n' }
|
|
180
|
+
|
|
127
181
|
// atomics...
|
|
182
|
+
// NOTE: these are not stored in seen thus are not re-referenced...
|
|
128
183
|
if(obj === null){
|
|
129
184
|
return NULL }
|
|
130
185
|
if(typeof(obj) != 'object'){
|
|
@@ -137,6 +192,8 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
137
192
|
INFINITY
|
|
138
193
|
: obj === -Infinity ?
|
|
139
194
|
NEG_INFINITY
|
|
195
|
+
// XXX might be a good idea to reference really long strings instead
|
|
196
|
+
// of storing each...
|
|
140
197
|
: JSON.stringify(obj, null, indent) }
|
|
141
198
|
|
|
142
199
|
// objects...
|
|
@@ -152,20 +209,20 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
152
209
|
for(var i=0; i < obj.length; i++){
|
|
153
210
|
elems.push(
|
|
154
211
|
i in obj ?
|
|
155
|
-
_serialize(obj[i], [...path, i], seen, indent, depth+1,
|
|
212
|
+
_serialize(obj[i], [...path, i], seen, indent, depth+1, options)
|
|
156
213
|
: EMPTY) }
|
|
157
214
|
} else if(obj instanceof Map){
|
|
158
215
|
pre = 'Map(['
|
|
159
216
|
post = '])'
|
|
160
217
|
elems = [
|
|
161
|
-
_serialize([...obj], path, seen, indent, depth,
|
|
218
|
+
_serialize([...obj], path, seen, indent, depth, options)
|
|
162
219
|
.slice(1, -1)
|
|
163
220
|
.trim() ]
|
|
164
221
|
} else if(obj instanceof Set){
|
|
165
222
|
pre = 'Set(['
|
|
166
223
|
post = '])'
|
|
167
224
|
elems = [
|
|
168
|
-
_serialize([...obj], path, seen, indent, depth,
|
|
225
|
+
_serialize([...obj], path, seen, indent, depth, options)
|
|
169
226
|
.slice(1, -1)
|
|
170
227
|
.trim() ]
|
|
171
228
|
} else {
|
|
@@ -175,7 +232,7 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
175
232
|
elems.push(`${
|
|
176
233
|
JSON.stringify(k)
|
|
177
234
|
}:${ indent != null ? ' ' : '' }${
|
|
178
|
-
_serialize(v, [...path, k], seen, indent, depth+1,
|
|
235
|
+
_serialize(v, [...path, k], seen, indent, depth+1, options)
|
|
179
236
|
// relevant for pretty-printing only...
|
|
180
237
|
.trimLeft()
|
|
181
238
|
}`) } }
|
|
@@ -196,8 +253,8 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
196
253
|
// user interface...
|
|
197
254
|
var serialize =
|
|
198
255
|
module.serialize =
|
|
199
|
-
function(obj, indent, depth=0,
|
|
200
|
-
return _serialize(obj, [], new Map(), indent, depth,
|
|
256
|
+
function(obj, indent, depth=0, options){
|
|
257
|
+
return _serialize(obj, [], new Map(), indent, depth, options) }
|
|
201
258
|
|
|
202
259
|
|
|
203
260
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
@@ -211,7 +268,7 @@ module.eJSON = {
|
|
|
211
268
|
|
|
212
269
|
0: 'number', 1: 'number', 2: 'number', 3: 'number', 4: 'number',
|
|
213
270
|
5: 'number', 6: 'number', 7: 'number', 8: 'number', 9: 'number',
|
|
214
|
-
'.': 'number', '-': 'number',
|
|
271
|
+
'.': 'number', '-': 'number', '+': 'number',
|
|
215
272
|
|
|
216
273
|
'[': 'array',
|
|
217
274
|
'{': 'object',
|
|
@@ -351,12 +408,37 @@ module.eJSON = {
|
|
|
351
408
|
&& str.slice(i, i+'-Infinity'.length) == '-Infinity'){
|
|
352
409
|
return [-Infinity, i+'-Infinity'.length, line] }
|
|
353
410
|
// numbers...
|
|
354
|
-
|
|
411
|
+
// XXX do we need to handle the odd case of 077 vs 088???
|
|
412
|
+
var j = i
|
|
413
|
+
var mode = 'dec'
|
|
414
|
+
if(str[j] == '0'
|
|
415
|
+
&& 'xXbBoO'.includes(str[j+1])){
|
|
416
|
+
mode = str[j+1]
|
|
417
|
+
j++ }
|
|
418
|
+
j++
|
|
355
419
|
while(j < str.length
|
|
356
420
|
&& (str[j] == '.'
|
|
421
|
+
// dec/oct/bin...
|
|
422
|
+
// XXX do we need to be pedantic and check the rest
|
|
423
|
+
// of the modes explicitly???
|
|
357
424
|
|| (str[j] >= '0'
|
|
358
|
-
&& str[j] <= '9')
|
|
425
|
+
&& str[j] <= '9')
|
|
426
|
+
// hex...
|
|
427
|
+
|| (mode == 'x'
|
|
428
|
+
&& ((str[j] >= 'a'
|
|
429
|
+
&& str[j] <= 'f')
|
|
430
|
+
|| (str[j] >= 'A'
|
|
431
|
+
&& str[j] <= 'F')))
|
|
432
|
+
// exponent...
|
|
433
|
+
|| str[j] == 'e'
|
|
434
|
+
|| str[j] == 'E')){
|
|
435
|
+
if('eE'.includes(str[j])
|
|
436
|
+
&& '+-'.includes(str[j+1])){
|
|
437
|
+
j++ }
|
|
359
438
|
j++ }
|
|
439
|
+
// BigInt...
|
|
440
|
+
if(str[j] == 'n'){
|
|
441
|
+
return [ BigInt(str.slice(i, j)), j+1, line ] }
|
|
360
442
|
return [ str.slice(i, j)*1, j, line ] },
|
|
361
443
|
// XXX TEST count \\n
|
|
362
444
|
string: function(state, path, match, str, i, line){
|
|
@@ -381,7 +463,10 @@ module.eJSON = {
|
|
|
381
463
|
if(j == str.length
|
|
382
464
|
&& str[j-1] != match){
|
|
383
465
|
this.error('Unexpected end of input wile looking fot "'+ match +'".', str, i, line) }
|
|
384
|
-
|
|
466
|
+
// NOTE: this is cheating -- instead of dancing around and
|
|
467
|
+
// replicating the full functionality of JSON.parse(..) we
|
|
468
|
+
// are simply falling back on it, as we did when serializing.
|
|
469
|
+
return [ JSON.parse(`"${str.slice(i+1, j)}"`), j+1, line ] },
|
|
385
470
|
identifier: function(state, path, match, str, i, line){
|
|
386
471
|
debug.lex('identifier', str, i, line)
|
|
387
472
|
if(!/[a-zA-Z_]/.test(str[i])){
|
|
@@ -543,7 +628,6 @@ module.eJSON = {
|
|
|
543
628
|
// NOTE: this uses eval(..) so care must be taken when enabling this...
|
|
544
629
|
func: function(state, path, match, str, i, line){
|
|
545
630
|
if(state.functions == null){
|
|
546
|
-
console.log('---', state)
|
|
547
631
|
this.error('Deserializing functions disabled.', str, i, line) }
|
|
548
632
|
|
|
549
633
|
debug.lex('function', str, i, line)
|
|
@@ -555,7 +639,7 @@ module.eJSON = {
|
|
|
555
639
|
this.error('Expected "," got "'+ str[i] +'"', str, i, line) }
|
|
556
640
|
i++
|
|
557
641
|
|
|
558
|
-
// func
|
|
642
|
+
// func index...
|
|
559
643
|
if(state.functions instanceof Array){
|
|
560
644
|
var [n, i, line] = this.number(state, path, str[i+1], str, i+1, line)
|
|
561
645
|
res = state.functions[n]
|
|
@@ -604,10 +688,10 @@ module.eJSON = {
|
|
|
604
688
|
return this[handler](state, path, match, str, i, line) },
|
|
605
689
|
|
|
606
690
|
|
|
607
|
-
parse: function(str,
|
|
691
|
+
parse: function(str, options={}){
|
|
608
692
|
|
|
609
693
|
// stage 1: build the object...
|
|
610
|
-
var state = {functions}
|
|
694
|
+
var state = {functions: options.functions}
|
|
611
695
|
var res = this.value(state, [], str)[0]
|
|
612
696
|
|
|
613
697
|
// stage 2: link the recursive structures...
|
|
@@ -615,13 +699,20 @@ module.eJSON = {
|
|
|
615
699
|
this.setItem(res, a, this.getItem(res, b)) }
|
|
616
700
|
|
|
617
701
|
return res },
|
|
702
|
+
|
|
703
|
+
// to comply with POLS...
|
|
704
|
+
stringify: serialize,
|
|
618
705
|
}
|
|
619
706
|
|
|
620
707
|
|
|
621
708
|
var deserialize =
|
|
622
709
|
module.deserialize =
|
|
623
|
-
function(str,
|
|
624
|
-
|
|
710
|
+
function(str, options){
|
|
711
|
+
options =
|
|
712
|
+
options === true ?
|
|
713
|
+
{functions: true}
|
|
714
|
+
: options
|
|
715
|
+
return eJSON.parse(str, options) }
|
|
625
716
|
|
|
626
717
|
|
|
627
718
|
|
|
@@ -630,18 +721,20 @@ function(str, functions){
|
|
|
630
721
|
|
|
631
722
|
var deepCopy =
|
|
632
723
|
module.deepCopy =
|
|
633
|
-
function(obj,
|
|
724
|
+
function(obj, funcs){
|
|
725
|
+
var options = {functions: funcs}
|
|
634
726
|
return deserialize(
|
|
635
|
-
serialize(obj, null, 0,
|
|
636
|
-
|
|
727
|
+
serialize(obj, null, 0, options),
|
|
728
|
+
options) }
|
|
637
729
|
|
|
638
730
|
|
|
639
731
|
var partialDeepCopy =
|
|
640
732
|
module.partialDeepCopy =
|
|
641
733
|
function(obj, funcs=[]){
|
|
734
|
+
var options = {functions: funcs}
|
|
642
735
|
return deserialize(
|
|
643
|
-
serialize(obj, null, 0,
|
|
644
|
-
|
|
736
|
+
serialize(obj, null, 0, options),
|
|
737
|
+
options) }
|
|
645
738
|
|
|
646
739
|
|
|
647
740
|
|
package/test.js
CHANGED
|
@@ -26,6 +26,8 @@ var eJSON = require('./serialize')
|
|
|
26
26
|
var json = true
|
|
27
27
|
var ejson = false
|
|
28
28
|
|
|
29
|
+
var pre_cycle = true
|
|
30
|
+
|
|
29
31
|
// XXX test whitespace handling...
|
|
30
32
|
var setups = test.Setups({
|
|
31
33
|
'true': function(assert){
|
|
@@ -43,17 +45,23 @@ var setups = test.Setups({
|
|
|
43
45
|
return ['123', json] },
|
|
44
46
|
'number-neg': function(assert){
|
|
45
47
|
return ['-123', json] },
|
|
48
|
+
//'number-pos': function(assert){
|
|
49
|
+
// return ['+123', json] },
|
|
50
|
+
'number-exp': function(assert){
|
|
51
|
+
return ['1e+100', json] },
|
|
46
52
|
'number-zero': function(assert){
|
|
47
53
|
return ['0', json] },
|
|
48
54
|
'float-a': function(assert){
|
|
49
55
|
return ['0.123', json] },
|
|
50
56
|
'float-b': function(assert){
|
|
51
57
|
return ['1.23', json] },
|
|
58
|
+
'bigint': function(assert){
|
|
59
|
+
return ['999999999999999999999n'] },
|
|
52
60
|
// XXX need a way to test this...
|
|
53
61
|
//'float-a': function(assert){
|
|
54
|
-
// return '.123' },
|
|
62
|
+
// return ['.123'] },
|
|
55
63
|
//'float-c': function(assert){
|
|
56
|
-
// return '123.' },
|
|
64
|
+
// return ['123.'] },
|
|
57
65
|
// XXX also test:
|
|
58
66
|
// hex/bin/orc/...
|
|
59
67
|
Infinity: function(assert){
|
|
@@ -64,6 +72,10 @@ var setups = test.Setups({
|
|
|
64
72
|
// XXX also test diffrerent quotations...
|
|
65
73
|
string: function(assert){
|
|
66
74
|
return ['"string"', json] },
|
|
75
|
+
'string-empty': function(assert){
|
|
76
|
+
return ['""', json] },
|
|
77
|
+
'string-multiline': function(assert){
|
|
78
|
+
return ['"string\\nstring\\nstring"', json] },
|
|
67
79
|
|
|
68
80
|
'array-empty': function(assert){
|
|
69
81
|
return ['[]', json] },
|
|
@@ -99,7 +111,6 @@ var setups = test.Setups({
|
|
|
99
111
|
return ['Map([[<RECURSIVE[]>,"value"]])'] },
|
|
100
112
|
'map-recursive-value': function(assert){
|
|
101
113
|
return ['Map([["key",<RECURSIVE[]>]])'] },
|
|
102
|
-
|
|
103
114
|
})
|
|
104
115
|
|
|
105
116
|
test.Modifiers({
|
|
@@ -163,7 +174,6 @@ test.Tests({
|
|
|
163
174
|
// XXX
|
|
164
175
|
},
|
|
165
176
|
|
|
166
|
-
//* XXX ERR
|
|
167
177
|
'partial-deep-copy': function(assert, [setup]){
|
|
168
178
|
var obj = eJSON.deserialize(setup, true)
|
|
169
179
|
var funcs = []
|
|
@@ -177,17 +187,85 @@ test.Tests({
|
|
|
177
187
|
//*/
|
|
178
188
|
})
|
|
179
189
|
|
|
180
|
-
/* XXX these break things...
|
|
181
190
|
test.Cases({
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
//
|
|
192
|
+
// Format:
|
|
193
|
+
// [
|
|
194
|
+
// [ "<extended-syntax>", "<JSON-syntax>" ],
|
|
195
|
+
// ...
|
|
196
|
+
// ]
|
|
197
|
+
//
|
|
198
|
+
// NOTE: these syntax variants are not output by .serialize(..) this it
|
|
199
|
+
// is less critical to test them in the main loop.
|
|
200
|
+
// XXX though it would be nice to do so...
|
|
201
|
+
tests: [
|
|
202
|
+
// numbers/floats...
|
|
203
|
+
['.123', '0.123'],
|
|
204
|
+
['123.', '123'],
|
|
205
|
+
['+123', '123'],
|
|
206
|
+
['123e100', '123e+100'],
|
|
207
|
+
['0xff', '255'],
|
|
208
|
+
|
|
209
|
+
// string quotes...
|
|
210
|
+
["'abc'", '"abc"'],
|
|
211
|
+
['`abc`', '"abc"'],
|
|
212
|
+
|
|
213
|
+
// arrays...
|
|
214
|
+
['[1,2,]', '[1,2]'],
|
|
215
|
+
|
|
216
|
+
// sparse arrays...
|
|
217
|
+
['[<empty>]', '[,]'],
|
|
218
|
+
['[1,2,<empty>]', '[1,2,,]'],
|
|
219
|
+
['[1,2,<empty>]', '[1,2,<empty>,]'],
|
|
220
|
+
],
|
|
221
|
+
'syntax-simplifications': function(assert){
|
|
222
|
+
var aa, bb
|
|
223
|
+
for(var [a, b] of this.tests){
|
|
224
|
+
assert(eJSON.serialize(aa = eJSON.deserialize(a)) == eJSON.serialize(bb = eJSON.deserialize(b)),
|
|
225
|
+
`"${ a }" and "${ b }" should deserialize to the samve value.`,
|
|
226
|
+
'got:', aa, 'and', bb, 'resp.') } },
|
|
227
|
+
|
|
228
|
+
_make_object_with_methods: function(){
|
|
229
|
+
var in_closure = 123
|
|
230
|
+
return {
|
|
231
|
+
stateful: function(){
|
|
232
|
+
return typeof(in_closure) != 'undefined' ?
|
|
233
|
+
'state_retained'
|
|
234
|
+
: 'state_lost' },
|
|
235
|
+
stateless: function(){
|
|
236
|
+
return 'stateless' },
|
|
237
|
+
} },
|
|
238
|
+
'deep-copy-function': function(assert){
|
|
239
|
+
var obj = this._make_object_with_methods()
|
|
240
|
+
var obj_copy = eJSON.deepCopy(obj, true)
|
|
241
|
+
|
|
242
|
+
// sanity checks...
|
|
243
|
+
assert(obj.stateless() == 'stateless')
|
|
244
|
+
assert(obj.stateful() == 'state_retained')
|
|
245
|
+
assert(obj_copy.stateless() == 'stateless')
|
|
246
|
+
|
|
247
|
+
// context should be lost...
|
|
248
|
+
assert(
|
|
249
|
+
obj_copy.stateful() == 'state_lost',
|
|
250
|
+
'Function closure not lost.')
|
|
251
|
+
assert(obj.stateful !== obj_copy.stateful,
|
|
252
|
+
'Function objects retained.') },
|
|
253
|
+
'partial-deep-copy-function': function(assert){
|
|
254
|
+
var obj = this._make_object_with_methods()
|
|
255
|
+
var obj_copy = eJSON.partialDeepCopy(obj)
|
|
256
|
+
|
|
257
|
+
// sanity checks...
|
|
258
|
+
assert(obj.stateless() == 'stateless')
|
|
259
|
+
assert(obj.stateful() == 'state_retained')
|
|
260
|
+
assert(obj_copy.stateless() == 'stateless')
|
|
261
|
+
|
|
262
|
+
// context should be retained...
|
|
263
|
+
assert(
|
|
264
|
+
obj_copy.stateful() == 'state_retained',
|
|
265
|
+
'Function closure lost.')
|
|
266
|
+
assert(obj.stateful === obj_copy.stateful,
|
|
267
|
+
'Function objects not retained.') },
|
|
189
268
|
})
|
|
190
|
-
//*/
|
|
191
269
|
|
|
192
270
|
|
|
193
271
|
//---------------------------------------------------------------------
|
package/serialize2.js
DELETED
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
/**********************************************************************
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
**********************************************************************/
|
|
6
|
-
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
7
|
-
(function(require){ var module={} // make module AMD/node compatible...
|
|
8
|
-
/*********************************************************************/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var EMPTY = '<empty>'
|
|
12
|
-
var NULL = 'null'
|
|
13
|
-
var UNDEFINED = 'undefined'
|
|
14
|
-
var NAN = 'NaN'
|
|
15
|
-
var INFINITY = 'Infinity'
|
|
16
|
-
var NEG_INFINITY = '-Infinity'
|
|
17
|
-
|
|
18
|
-
var RECURSIVE = '<RECURSIVE%>'
|
|
19
|
-
|
|
20
|
-
var FUNCTION = '<FUNCTION[%]>'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
25
|
-
|
|
26
|
-
module.DEBUG = false
|
|
27
|
-
|
|
28
|
-
var DEBUG_PREFIX = '---'
|
|
29
|
-
|
|
30
|
-
var debug = {
|
|
31
|
-
context: 16,
|
|
32
|
-
|
|
33
|
-
log: function(...args){
|
|
34
|
-
if(!module.DEBUG){
|
|
35
|
-
return }
|
|
36
|
-
return console.log(DEBUG_PREFIX, ...arguments) },
|
|
37
|
-
lex: function(name, str, i, line){
|
|
38
|
-
return this.log(name +':', str.slice(i, i+this.context) +'...') },
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
//---------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
// serialize(obj[, indent[, depth]])
|
|
46
|
-
// -> str
|
|
47
|
-
//
|
|
48
|
-
// indent can be:
|
|
49
|
-
// number - number of spaces to use for indent
|
|
50
|
-
// string - string to use for indenting
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// _serialize(obj, base_path, seen, indent, depth, functions)
|
|
54
|
-
// -> str
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// Paths
|
|
58
|
-
// A path is a chain of indexes/keys leading to a specific object in
|
|
59
|
-
// tree.
|
|
60
|
-
//
|
|
61
|
-
// the root object is referenced by an empty path array.
|
|
62
|
-
//
|
|
63
|
-
// For sets, positional indexes are used, i.e. a set is treated like
|
|
64
|
-
// an array ov values.
|
|
65
|
-
//
|
|
66
|
-
// For maps a two number index is used with the first being the position
|
|
67
|
-
// of the item and the second indicated if the target is the key or the
|
|
68
|
-
// value. i.e. a map is treated like an array of key value pairs.
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// Examples:
|
|
72
|
-
// Object paths
|
|
73
|
-
// ---------------------------------------------------------------
|
|
74
|
-
// obj = [ <- []
|
|
75
|
-
// null, <- [0]
|
|
76
|
-
// [
|
|
77
|
-
// 'a',
|
|
78
|
-
// 'b', <- [1, 1]
|
|
79
|
-
// 'c',
|
|
80
|
-
// ],
|
|
81
|
-
// Set([
|
|
82
|
-
// 1,
|
|
83
|
-
// 2,
|
|
84
|
-
// 3, <- [2, 2]
|
|
85
|
-
// ]),
|
|
86
|
-
// Map([i <- [3] +----- index of elemnt in map
|
|
87
|
-
// [ / +--- 0 means key
|
|
88
|
-
// 'key', <- [3, 0, 0]
|
|
89
|
-
// 'value' <- [3, 0, 1]
|
|
90
|
-
// ], +--- 1 means value
|
|
91
|
-
// [
|
|
92
|
-
// [
|
|
93
|
-
// 123 <- [3, 1, 0, 0]
|
|
94
|
-
// ],
|
|
95
|
-
// 'got tired of thinking up names',
|
|
96
|
-
// ],
|
|
97
|
-
// ]),
|
|
98
|
-
// ]
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// XXX BUG: using non-whitespace as indent breaks the depth of the first
|
|
102
|
-
// or last elements in sequences
|
|
103
|
-
// ...breaks .trim*() in Map/Set/Object...
|
|
104
|
-
var _serialize =
|
|
105
|
-
module._serialize =
|
|
106
|
-
function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
107
|
-
// recursive...
|
|
108
|
-
var p = seen.get(obj)
|
|
109
|
-
if(p != null){
|
|
110
|
-
// NOTE: _serialize(..) is always printed flat here, regardless of indent/depth...
|
|
111
|
-
return RECURSIVE.replace('%', _serialize(p)) }
|
|
112
|
-
|
|
113
|
-
// functions...
|
|
114
|
-
// NOTE: we are storing function length to avoid parsing the function...
|
|
115
|
-
// NOTE: storing length is a potential attack vector...
|
|
116
|
-
if(typeof(obj) == 'function'){
|
|
117
|
-
seen.set(obj, path)
|
|
118
|
-
// if functions array is given add function to it and store its
|
|
119
|
-
// index in the serialized data...
|
|
120
|
-
if(functions != null){
|
|
121
|
-
functions.push(obj)
|
|
122
|
-
obj = functions.length-1 }
|
|
123
|
-
var s = '('+ obj.toString() +')'
|
|
124
|
-
return FUNCTION
|
|
125
|
-
.replace('%', s.length +','+ s) }
|
|
126
|
-
|
|
127
|
-
// atomics...
|
|
128
|
-
if(obj === null){
|
|
129
|
-
return NULL }
|
|
130
|
-
if(typeof(obj) != 'object'){
|
|
131
|
-
return typeof(obj) == 'number'
|
|
132
|
-
&& isNaN(obj) ?
|
|
133
|
-
NAN
|
|
134
|
-
: obj === undefined ?
|
|
135
|
-
UNDEFINED
|
|
136
|
-
: obj === Infinity ?
|
|
137
|
-
INFINITY
|
|
138
|
-
: obj === -Infinity ?
|
|
139
|
-
NEG_INFINITY
|
|
140
|
-
: JSON.stringify(obj, null, indent) }
|
|
141
|
-
|
|
142
|
-
// objects...
|
|
143
|
-
seen.set(obj, path)
|
|
144
|
-
|
|
145
|
-
var elems = []
|
|
146
|
-
var pre = ''
|
|
147
|
-
var join = ','
|
|
148
|
-
var post = ''
|
|
149
|
-
if(obj instanceof Array){
|
|
150
|
-
pre = '['
|
|
151
|
-
post = ']'
|
|
152
|
-
for(var i=0; i < obj.length; i++){
|
|
153
|
-
elems.push(
|
|
154
|
-
i in obj ?
|
|
155
|
-
_serialize(obj[i], [...path, i], seen, indent, depth+1, functions)
|
|
156
|
-
: EMPTY) }
|
|
157
|
-
} else if(obj instanceof Map){
|
|
158
|
-
pre = 'Map(['
|
|
159
|
-
post = '])'
|
|
160
|
-
elems = [
|
|
161
|
-
_serialize([...obj], path, seen, indent, depth, functions)
|
|
162
|
-
.slice(1, -1)
|
|
163
|
-
.trim() ]
|
|
164
|
-
} else if(obj instanceof Set){
|
|
165
|
-
pre = 'Set(['
|
|
166
|
-
post = '])'
|
|
167
|
-
elems = [
|
|
168
|
-
_serialize([...obj], path, seen, indent, depth, functions)
|
|
169
|
-
.slice(1, -1)
|
|
170
|
-
.trim() ]
|
|
171
|
-
} else {
|
|
172
|
-
pre = '{'
|
|
173
|
-
post = '}'
|
|
174
|
-
for(var [k, v] of Object.entries(obj)){
|
|
175
|
-
elems.push(`${
|
|
176
|
-
JSON.stringify(k)
|
|
177
|
-
}:${ indent != null ? ' ' : '' }${
|
|
178
|
-
_serialize(v, [...path, k], seen, indent, depth+1, functions)
|
|
179
|
-
// relevant for pretty-printing only...
|
|
180
|
-
.trimLeft()
|
|
181
|
-
}`) } }
|
|
182
|
-
|
|
183
|
-
// handle indent...
|
|
184
|
-
if(indent != null){
|
|
185
|
-
i = indent.repeat(depth)
|
|
186
|
-
s = i + indent
|
|
187
|
-
if(elems.length > 0){
|
|
188
|
-
pre = pre + '\n' + s
|
|
189
|
-
post = '\n' + i + post
|
|
190
|
-
// XXX set limit for number of elements to keep horizontal...
|
|
191
|
-
// ...also account for element length...
|
|
192
|
-
join = join + '\n' + s } }
|
|
193
|
-
|
|
194
|
-
return pre+ elems.join(join) +post }
|
|
195
|
-
|
|
196
|
-
// user interface...
|
|
197
|
-
var serialize =
|
|
198
|
-
module.serialize =
|
|
199
|
-
function(obj, indent, depth=0, functions){
|
|
200
|
-
return _serialize(obj, [], new Map(), indent, depth, functions) }
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
204
|
-
|
|
205
|
-
// XXX better error handling...
|
|
206
|
-
// XXX try and make this single stage (see notes for : .recursive(..))
|
|
207
|
-
var eJSON =
|
|
208
|
-
module.eJSON = {
|
|
209
|
-
chars: {
|
|
210
|
-
'"': 'string', "'": 'string', '`': 'string',
|
|
211
|
-
|
|
212
|
-
0: 'number', 1: 'number', 2: 'number', 3: 'number', 4: 'number',
|
|
213
|
-
5: 'number', 6: 'number', 7: 'number', 8: 'number', 9: 'number',
|
|
214
|
-
'.': 'number', '-': 'number',
|
|
215
|
-
|
|
216
|
-
'[': 'array',
|
|
217
|
-
'{': 'object',
|
|
218
|
-
},
|
|
219
|
-
words: {
|
|
220
|
-
true: true,
|
|
221
|
-
false: false,
|
|
222
|
-
|
|
223
|
-
Infinity: 'number',
|
|
224
|
-
|
|
225
|
-
'Set(': 'set',
|
|
226
|
-
'Map(': 'map',
|
|
227
|
-
|
|
228
|
-
null: null,
|
|
229
|
-
undefined: undefined,
|
|
230
|
-
NaN: NaN,
|
|
231
|
-
|
|
232
|
-
'<empty>': 'empty',
|
|
233
|
-
'<RECURSIVE': 'recursive',
|
|
234
|
-
|
|
235
|
-
'<FUNCTION[': 'func',
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// generic helpers...
|
|
240
|
-
//
|
|
241
|
-
// XXX need to keep the context constrained to one line...
|
|
242
|
-
context_size: 16,
|
|
243
|
-
error: function(msg, str, i, line){
|
|
244
|
-
var pre = i
|
|
245
|
-
var post = i
|
|
246
|
-
while(pre > 0
|
|
247
|
-
&& i - pre <= this.context_size
|
|
248
|
-
&& str[pre] != '\n'){
|
|
249
|
-
pre-- }
|
|
250
|
-
while(post < str.length
|
|
251
|
-
&& post - i <= this.context_size
|
|
252
|
-
&& str[post] != '\n'){
|
|
253
|
-
post++ }
|
|
254
|
-
throw new SyntaxError(`${ msg }\n`
|
|
255
|
-
+` ${line}: ${ str.slice(pre, post) }\n`
|
|
256
|
-
+` ${ ' '.repeat( i - pre + ((line + ': ').length) ) }^`) },
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
// ._getItem(obj, path)
|
|
260
|
-
// -> [path, obj]
|
|
261
|
-
//
|
|
262
|
-
// NOTE: we are returning path here because we need to distinguish
|
|
263
|
-
// incomplete paths that we can infer the container from and
|
|
264
|
-
// complete paths, e.g:
|
|
265
|
-
// For
|
|
266
|
-
// m = [new Map([[1,2], [2,3]])]
|
|
267
|
-
// Both of the below will return the map and the correct path ([0]):
|
|
268
|
-
// eJSON._getItem(m, [0, 1])
|
|
269
|
-
// eJSON._getItem(m, [0])
|
|
270
|
-
// But we need to destinguish between the two cases when
|
|
271
|
-
// writing to a map (see: .setItem(..))
|
|
272
|
-
_getItem: function(obj, path){
|
|
273
|
-
for(var i=0; i<path.length; i++){
|
|
274
|
-
var k = path[i]
|
|
275
|
-
// speacial case: incomplete map index...
|
|
276
|
-
if( obj instanceof Map
|
|
277
|
-
&& i == path.length-1){
|
|
278
|
-
return [path.slice(0, -1), obj] }
|
|
279
|
-
|
|
280
|
-
obj = obj instanceof Set ?
|
|
281
|
-
[...obj][k]
|
|
282
|
-
: obj instanceof Map ?
|
|
283
|
-
[...obj][k][path[++i]]
|
|
284
|
-
: obj[k] }
|
|
285
|
-
return [path, obj] },
|
|
286
|
-
//
|
|
287
|
-
// .getItem(obj, path)
|
|
288
|
-
// -> obj
|
|
289
|
-
//
|
|
290
|
-
// NOTE: this is a POLS wrapper of ._getItem(..)
|
|
291
|
-
getItem: function(obj, path){
|
|
292
|
-
return this._getItem(...arguments)[1] },
|
|
293
|
-
// NOTE: this behaves in a similar way to normal key value assignment,
|
|
294
|
-
// i.e. replacing items if they exist, this is not a problem here
|
|
295
|
-
// as in the general case we are assigning over a placeholder
|
|
296
|
-
// object...
|
|
297
|
-
// NOTE: as a side-effect this maintains element oreder even in object
|
|
298
|
-
// with no direct concept of element order, like objects, sets and
|
|
299
|
-
// maps.
|
|
300
|
-
// NOTE: some operations envolve rewriting the container elements so
|
|
301
|
-
// are not as fast, namely writing set elements and map keys.
|
|
302
|
-
setItem: function(obj, path, value){
|
|
303
|
-
var [p, parent] = this._getItem(obj, path.slice(0, -1))
|
|
304
|
-
var k = path.at(-1)
|
|
305
|
-
if(parent instanceof Set){
|
|
306
|
-
var elems = [...parent]
|
|
307
|
-
elems[k] = value
|
|
308
|
-
// we cant to keep the order...
|
|
309
|
-
parent.clear()
|
|
310
|
-
for(var e of elems){
|
|
311
|
-
parent.add(e) }
|
|
312
|
-
} else if(parent instanceof Map){
|
|
313
|
-
if(path.length-2 !== p.length){
|
|
314
|
-
throw new Error('.setItem(..): incomplete path.') }
|
|
315
|
-
var i = path.at(-2)
|
|
316
|
-
// replace the index...
|
|
317
|
-
if(k == 0){
|
|
318
|
-
var elems = [...parent]
|
|
319
|
-
elems[i][0] = value
|
|
320
|
-
parent.clear()
|
|
321
|
-
for(var e of elems){
|
|
322
|
-
parent.set(...e) }
|
|
323
|
-
// set the value...
|
|
324
|
-
} else {
|
|
325
|
-
parent.set([...parent][i][0], value) }
|
|
326
|
-
} else {
|
|
327
|
-
parent[k] = value }
|
|
328
|
-
return value },
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
WHITESPACE: ' \t\n',
|
|
333
|
-
skipWhitespace: function(str, i, line){
|
|
334
|
-
while(i < str.length
|
|
335
|
-
&& this.WHITESPACE.includes(str[i])){
|
|
336
|
-
if(str[i] == '\n'){
|
|
337
|
-
line++ }
|
|
338
|
-
i++ }
|
|
339
|
-
return [i, line] },
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
// .handler(match, str, i, line)
|
|
343
|
-
// -> [value, i, line]
|
|
344
|
-
//
|
|
345
|
-
number: function(state, path, match, str, i, line){
|
|
346
|
-
debug.lex('number', str, i, line)
|
|
347
|
-
// special cases..,
|
|
348
|
-
if(match == 'Infinity'){
|
|
349
|
-
return [Infinity, i+'Infinity'.length, line] }
|
|
350
|
-
if(match == '-'
|
|
351
|
-
&& str.slice(i, i+'-Infinity'.length) == '-Infinity'){
|
|
352
|
-
return [-Infinity, i+'-Infinity'.length, line] }
|
|
353
|
-
// numbers...
|
|
354
|
-
var j = i+1
|
|
355
|
-
while(j < str.length
|
|
356
|
-
&& (str[j] == '.'
|
|
357
|
-
|| (str[j] >= '0'
|
|
358
|
-
&& str[j] <= '9'))){
|
|
359
|
-
j++ }
|
|
360
|
-
return [ str.slice(i, j)*1, j, line ] },
|
|
361
|
-
// XXX TEST count \\n
|
|
362
|
-
string: function(state, path, match, str, i, line){
|
|
363
|
-
debug.lex('string', str, i, line)
|
|
364
|
-
var j = i+1
|
|
365
|
-
while(j < str.length
|
|
366
|
-
&& str[j] != match){
|
|
367
|
-
// newlines...
|
|
368
|
-
if(str[j] == '\n'){
|
|
369
|
-
line++ }
|
|
370
|
-
// escaped newlines...
|
|
371
|
-
if(str[j] == '\\'
|
|
372
|
-
&& j+1 < str.length
|
|
373
|
-
&& str[j+1] == 'n'){
|
|
374
|
-
line++ }
|
|
375
|
-
// skip escaped quotes...
|
|
376
|
-
if(str[j] == '\\'
|
|
377
|
-
&& j+1 < str.length
|
|
378
|
-
&& str[j+1] == match){
|
|
379
|
-
j++ }
|
|
380
|
-
j++ }
|
|
381
|
-
if(j == str.length
|
|
382
|
-
&& str[j-1] != match){
|
|
383
|
-
this.error('Unexpected end of input wile looking fot "'+ match +'".', str, i, line) }
|
|
384
|
-
return [ str.slice(i+1, j), j+1, line ] },
|
|
385
|
-
identifier: function(state, path, match, str, i, line){
|
|
386
|
-
debug.lex('identifier', str, i, line)
|
|
387
|
-
if(!/[a-zA-Z_]/.test(str[i])){
|
|
388
|
-
this.error('Not an identifier: "'+ str[i] +'"', str, i, line) }
|
|
389
|
-
var j = i+1
|
|
390
|
-
while(j < str.length
|
|
391
|
-
&& /[a-zA-Z0-9_]/.test(str[j])){
|
|
392
|
-
j++ }
|
|
393
|
-
return [ str.slice(i, j), j, line ] },
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
// handler(res, index, str, i, line)
|
|
397
|
-
// -> [res, i, line]
|
|
398
|
-
//
|
|
399
|
-
sequence: function(state, path, str, i, line, end, handler, initial=[]){
|
|
400
|
-
var index = 0
|
|
401
|
-
while(i < str.length){
|
|
402
|
-
;[i, line] = this.skipWhitespace(str, i, line)
|
|
403
|
-
|
|
404
|
-
// done...
|
|
405
|
-
if(str.slice(i, i+end.length) == end){
|
|
406
|
-
return [initial, i+end.length, line] }
|
|
407
|
-
|
|
408
|
-
// empty...
|
|
409
|
-
if(str[i] == ','){
|
|
410
|
-
index++
|
|
411
|
-
i++
|
|
412
|
-
// XXX this feels hackish -- can this be deligated to the handler???
|
|
413
|
-
initial instanceof Array
|
|
414
|
-
&& initial.length++
|
|
415
|
-
continue }
|
|
416
|
-
if(str.slice(i, i+EMPTY.length) == EMPTY){
|
|
417
|
-
index++
|
|
418
|
-
i += EMPTY.length
|
|
419
|
-
if(str[i] == ','){
|
|
420
|
-
i++ }
|
|
421
|
-
// XXX this feels hackish -- can this be deligated to the handler???
|
|
422
|
-
initial instanceof Array
|
|
423
|
-
&& initial.length++
|
|
424
|
-
continue }
|
|
425
|
-
|
|
426
|
-
// end of input...
|
|
427
|
-
if(i >= str.length-1){
|
|
428
|
-
break }
|
|
429
|
-
|
|
430
|
-
// value...
|
|
431
|
-
;[initial, i, line] = handler.call(this, initial, index, str, i, line)
|
|
432
|
-
|
|
433
|
-
;[i, line] = this.skipWhitespace(str, i, line)
|
|
434
|
-
if(str[i] == ','){
|
|
435
|
-
i++ }
|
|
436
|
-
index++ }
|
|
437
|
-
|
|
438
|
-
// XXX better message -- show starting seq...
|
|
439
|
-
this.error('Unexpected end of input wile looking for "'+ end +'".', str, i, line) },
|
|
440
|
-
|
|
441
|
-
array: function(state, path, match, str, i, line){
|
|
442
|
-
debug.lex('array', str, i, line)
|
|
443
|
-
return this.sequence(
|
|
444
|
-
state, path, str, i+1, line,
|
|
445
|
-
']',
|
|
446
|
-
function(res, index, str, i, line){
|
|
447
|
-
var obj
|
|
448
|
-
;[obj, i, line] = this.value(state, [...path, index], str, i, line)
|
|
449
|
-
res[index] = obj
|
|
450
|
-
return [res, i, line] }) },
|
|
451
|
-
object: function(state, path, match, str, i, line){
|
|
452
|
-
debug.lex('object', str, i, line)
|
|
453
|
-
return this.sequence(
|
|
454
|
-
state, path, str, i+1, line,
|
|
455
|
-
'}',
|
|
456
|
-
function(res, index, str, i, line){
|
|
457
|
-
var obj, key
|
|
458
|
-
// key...
|
|
459
|
-
;[key, i, line] = '\'"`'.includes(str[i]) ?
|
|
460
|
-
this.string(state, path, str[i], str, i, line)
|
|
461
|
-
: this.identifier(state, path, str[i], str, i, line)
|
|
462
|
-
|
|
463
|
-
// ':'...
|
|
464
|
-
;[i, line] = this.skipWhitespace(str, i, line)
|
|
465
|
-
if(str[i] != ':'){
|
|
466
|
-
this.error('Expected ":", got "'+ str[i] +'".', str, i, line) }
|
|
467
|
-
i++
|
|
468
|
-
;[i, line] = this.skipWhitespace(str, i, line)
|
|
469
|
-
|
|
470
|
-
// value...
|
|
471
|
-
;[obj, i, line] = this.value(state, [...path, key], str, i, line)
|
|
472
|
-
res[key] = obj
|
|
473
|
-
return [res, i, line] },
|
|
474
|
-
{}) },
|
|
475
|
-
|
|
476
|
-
set: function(state, path, match, str, i, line){
|
|
477
|
-
debug.lex('set', str, i, line)
|
|
478
|
-
i += match.length
|
|
479
|
-
;[res, i, line] = this.sequence(
|
|
480
|
-
state, path,
|
|
481
|
-
str, i+1, line,
|
|
482
|
-
']',
|
|
483
|
-
function(res, index, str, i, line){
|
|
484
|
-
var obj
|
|
485
|
-
;[obj, i, line] = this.value(
|
|
486
|
-
state,
|
|
487
|
-
[...path, index],
|
|
488
|
-
str, i, line)
|
|
489
|
-
res.add(obj)
|
|
490
|
-
return [res, i, line] },
|
|
491
|
-
new Set())
|
|
492
|
-
if(str[i] != ')'){
|
|
493
|
-
this.error('Expected ")", got "'+ str[i] +'".', str, i, line) }
|
|
494
|
-
return [res, i+1, line] },
|
|
495
|
-
map: function(state, path, match, str, i, line){
|
|
496
|
-
debug.lex('map', str, i, line)
|
|
497
|
-
i += match.length
|
|
498
|
-
;[res, i, line] = this.sequence(
|
|
499
|
-
state, path,
|
|
500
|
-
str, i+1, line,
|
|
501
|
-
']',
|
|
502
|
-
function(res, index, str, i, line){
|
|
503
|
-
debug.lex(' map-content', str, i, line)
|
|
504
|
-
var obj
|
|
505
|
-
;[[key, value], i, line] = this.value(
|
|
506
|
-
state,
|
|
507
|
-
[...path, index],
|
|
508
|
-
str, i, line)
|
|
509
|
-
res.set(key, value)
|
|
510
|
-
return [res, i, line] },
|
|
511
|
-
new Map())
|
|
512
|
-
if(str[i] != ')'){
|
|
513
|
-
this.error('Expected ")", got "'+ str[i] +'".', str, i, line) }
|
|
514
|
-
return [res, i+1, line] },
|
|
515
|
-
|
|
516
|
-
// XXX should this be done inline or on a separate stage?
|
|
517
|
-
// for inline we need these:
|
|
518
|
-
// - all containers must be placed as soon as they are created
|
|
519
|
-
// "return" before the items are processed...
|
|
520
|
-
// - containers must be "filled" as the items are parsed
|
|
521
|
-
// like .array(..) and .object(..) but not like .map(..) and .set(..)
|
|
522
|
-
// - thread the root object...
|
|
523
|
-
// alternatively, for a two stage process we need to:
|
|
524
|
-
// - have a mechanism to write placeholders
|
|
525
|
-
// - remember their positions (paths)
|
|
526
|
-
// - build/get said path
|
|
527
|
-
// XXX another strategy would be to have an path-object map and
|
|
528
|
-
// directly reference that -- this would eliminate the need for
|
|
529
|
-
// stage two... (XXX TEST)
|
|
530
|
-
// ...need to use serialized paths as keys...
|
|
531
|
-
recursive: function(state, path, match, str, i, line){
|
|
532
|
-
debug.lex('recursive', str, i, line)
|
|
533
|
-
return this.sequence(
|
|
534
|
-
state, path, str, i+match.length, line,
|
|
535
|
-
'>',
|
|
536
|
-
function(res, index, str, i, line){
|
|
537
|
-
var obj
|
|
538
|
-
;[obj, i, line] = this.array(state, [...path, index], '[', str, i, line)
|
|
539
|
-
var rec = state.recursive ??= []
|
|
540
|
-
rec.push([path, obj])
|
|
541
|
-
return [{}, i, line] }) },
|
|
542
|
-
|
|
543
|
-
// NOTE: this uses eval(..) so care must be taken when enabling this...
|
|
544
|
-
func: function(state, path, match, str, i, line){
|
|
545
|
-
if(state.functions == null){
|
|
546
|
-
this.error('Deserializing functions disabled.', str, i, line) }
|
|
547
|
-
|
|
548
|
-
debug.lex('function', str, i, line)
|
|
549
|
-
var res
|
|
550
|
-
// length...
|
|
551
|
-
i += match.length
|
|
552
|
-
var [l, i, line] = this.number(state, path, str[i], str, i, line)
|
|
553
|
-
if(str[i] != ','){
|
|
554
|
-
this.error('Expected "," got "'+ str[i] +'"', str, i, line) }
|
|
555
|
-
i++
|
|
556
|
-
|
|
557
|
-
// func ref...
|
|
558
|
-
if(state.functions instanceof Array){
|
|
559
|
-
var [n, i, line] = this.number(state, path, str[i+1], str, i+1, line)
|
|
560
|
-
res = state.functions[n]
|
|
561
|
-
// func code...
|
|
562
|
-
} else {
|
|
563
|
-
var code = str.slice(i, i+l)
|
|
564
|
-
i += l
|
|
565
|
-
line += code.split(/\n/g).length
|
|
566
|
-
if(str.slice(i, i+2) == ']>'){
|
|
567
|
-
res = eval?.(code) } }
|
|
568
|
-
|
|
569
|
-
if(str.slice(i, i+2) != ']>'){
|
|
570
|
-
this.error('Expected "]>" got "'+ str.slice(i, i+2) +'"', str, i, line) }
|
|
571
|
-
return [res, i+2, line] },
|
|
572
|
-
|
|
573
|
-
value: function(state, path, str, i=0, line=0){
|
|
574
|
-
|
|
575
|
-
;[i, line] = this.skipWhitespace(str, i, line)
|
|
576
|
-
|
|
577
|
-
// get handler...
|
|
578
|
-
var NONE = {}
|
|
579
|
-
var handler = NONE
|
|
580
|
-
var match = str[i]
|
|
581
|
-
if(match in this.chars){
|
|
582
|
-
handler = this.chars[match]
|
|
583
|
-
} else {
|
|
584
|
-
for(match in this.words){
|
|
585
|
-
if(match == str.slice(i, i+match.length)){
|
|
586
|
-
handler = this.words[match]
|
|
587
|
-
break } } }
|
|
588
|
-
|
|
589
|
-
// syntax error...
|
|
590
|
-
if(handler === NONE){
|
|
591
|
-
var context = 8
|
|
592
|
-
var pre = Math.max(i-context, 0)
|
|
593
|
-
var post = Math.min(i+context, str.length)
|
|
594
|
-
this.error(`Unexpected: ${ str.slice(i, i+10) }`, str, i, line) }
|
|
595
|
-
|
|
596
|
-
// value...
|
|
597
|
-
if(typeof(handler) != 'string'){
|
|
598
|
-
return [handler, i+match.length, line] }
|
|
599
|
-
// func...
|
|
600
|
-
return this[handler](state, path, match, str, i, line) },
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
parse: function(str, functions){
|
|
604
|
-
|
|
605
|
-
// stage 1: build the object...
|
|
606
|
-
var state = {functions}
|
|
607
|
-
var res = this.value(state, [], str)[0]
|
|
608
|
-
|
|
609
|
-
// stage 2: link the recursive structures...
|
|
610
|
-
for(var [a, b] of state.recursive ?? []){
|
|
611
|
-
this.setItem(res, a, this.getItem(res, b)) }
|
|
612
|
-
|
|
613
|
-
return res },
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
var deserialize =
|
|
618
|
-
module.deserialize =
|
|
619
|
-
function(str, functions){
|
|
620
|
-
return eJSON.parse(str, functions) }
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
//---------------------------------------------------------------------
|
|
625
|
-
// utils...
|
|
626
|
-
|
|
627
|
-
var deepCopy =
|
|
628
|
-
module.deepCopy =
|
|
629
|
-
function(obj){
|
|
630
|
-
return deserialize(
|
|
631
|
-
serialize(obj)) }
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
var semiDeepCopy =
|
|
635
|
-
module.semiDeepCopy =
|
|
636
|
-
function(obj){
|
|
637
|
-
var funcs = []
|
|
638
|
-
return deserialize(
|
|
639
|
-
serialize(obj, null, 0, funcs),
|
|
640
|
-
funcs) }
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
/**********************************************************************
|
|
645
|
-
* vim:set ts=4 sw=4 nowrap : */ return module })
|