ig-serialize 1.0.0 → 1.0.4
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 +54 -2
- package/package.json +1 -1
- package/serialize.js +63 -26
- package/test.js +74 -2
- package/serialize2.js +0 -645
package/README.md
CHANGED
|
@@ -1,2 +1,54 @@
|
|
|
1
|
-
# serilize.js
|
|
2
|
-
|
|
1
|
+
# serilize.js: Extended JSON serilization
|
|
2
|
+
|
|
3
|
+
This extends the default JSON serialization adding the following:
|
|
4
|
+
- Recursive data structure serialization
|
|
5
|
+
- `undefined`/`NaN` serialization
|
|
6
|
+
- `Set`/`Map` serialization
|
|
7
|
+
- Function serialization (off by default)
|
|
8
|
+
- Deep and partial-deep cleen object copy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Motivation
|
|
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
|
+
|
|
53
|
+
|
|
54
|
+
|
package/package.json
CHANGED
package/serialize.js
CHANGED
|
@@ -41,8 +41,11 @@ var debug = {
|
|
|
41
41
|
|
|
42
42
|
//---------------------------------------------------------------------
|
|
43
43
|
|
|
44
|
+
module.STRING_LENGTH_REF = 64
|
|
45
|
+
|
|
44
46
|
//
|
|
45
|
-
// serialize(obj[,
|
|
47
|
+
// serialize(obj[, options])
|
|
48
|
+
// serialize(obj[, indent[, depth[, options]]])
|
|
46
49
|
// -> str
|
|
47
50
|
//
|
|
48
51
|
// indent can be:
|
|
@@ -50,7 +53,7 @@ var debug = {
|
|
|
50
53
|
// string - string to use for indenting
|
|
51
54
|
//
|
|
52
55
|
//
|
|
53
|
-
// _serialize(obj, base_path, seen, indent, depth,
|
|
56
|
+
// _serialize(obj, base_path, seen, indent, depth, options)
|
|
54
57
|
// -> str
|
|
55
58
|
//
|
|
56
59
|
//
|
|
@@ -103,7 +106,11 @@ var debug = {
|
|
|
103
106
|
// ...breaks .trim*() in Map/Set/Object...
|
|
104
107
|
var _serialize =
|
|
105
108
|
module._serialize =
|
|
106
|
-
function(obj, path=[], seen=new Map(), indent, depth=0,
|
|
109
|
+
function(obj, path=[], seen=new Map(), indent, depth=0, options={}){
|
|
110
|
+
var string_length_ref =
|
|
111
|
+
options.string_length_ref
|
|
112
|
+
?? module.STRING_LENGTH_REF
|
|
113
|
+
|
|
107
114
|
// recursive...
|
|
108
115
|
var p = seen.get(obj)
|
|
109
116
|
if(p != null){
|
|
@@ -117,14 +124,24 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
117
124
|
seen.set(obj, path)
|
|
118
125
|
// if functions array is given add function to it and store its
|
|
119
126
|
// index in the serialized data...
|
|
120
|
-
if(functions
|
|
121
|
-
functions.push(obj)
|
|
122
|
-
obj = functions.length-1 }
|
|
127
|
+
if(options.functions instanceof Array){
|
|
128
|
+
options.functions.push(obj)
|
|
129
|
+
obj = options.functions.length-1 }
|
|
123
130
|
var s = '('+ obj.toString() +')'
|
|
124
131
|
return FUNCTION
|
|
125
132
|
.replace('%', s.length +','+ s) }
|
|
126
133
|
|
|
134
|
+
// special case: long strings...
|
|
135
|
+
if(typeof(obj) == 'string'
|
|
136
|
+
&& obj.length > string_length_ref){
|
|
137
|
+
seen.set(obj, path) }
|
|
138
|
+
// BigInt...
|
|
139
|
+
if(typeof(obj) == 'bigint'){
|
|
140
|
+
seen.set(obj, path)
|
|
141
|
+
return obj.toString() +'n' }
|
|
142
|
+
|
|
127
143
|
// atomics...
|
|
144
|
+
// NOTE: these are not stored in seen thus are not re-referenced...
|
|
128
145
|
if(obj === null){
|
|
129
146
|
return NULL }
|
|
130
147
|
if(typeof(obj) != 'object'){
|
|
@@ -137,6 +154,8 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
137
154
|
INFINITY
|
|
138
155
|
: obj === -Infinity ?
|
|
139
156
|
NEG_INFINITY
|
|
157
|
+
// XXX might be a good idea to reference really long strings instead
|
|
158
|
+
// of storing each...
|
|
140
159
|
: JSON.stringify(obj, null, indent) }
|
|
141
160
|
|
|
142
161
|
// objects...
|
|
@@ -152,20 +171,20 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
152
171
|
for(var i=0; i < obj.length; i++){
|
|
153
172
|
elems.push(
|
|
154
173
|
i in obj ?
|
|
155
|
-
_serialize(obj[i], [...path, i], seen, indent, depth+1,
|
|
174
|
+
_serialize(obj[i], [...path, i], seen, indent, depth+1, options)
|
|
156
175
|
: EMPTY) }
|
|
157
176
|
} else if(obj instanceof Map){
|
|
158
177
|
pre = 'Map(['
|
|
159
178
|
post = '])'
|
|
160
179
|
elems = [
|
|
161
|
-
_serialize([...obj], path, seen, indent, depth,
|
|
180
|
+
_serialize([...obj], path, seen, indent, depth, options)
|
|
162
181
|
.slice(1, -1)
|
|
163
182
|
.trim() ]
|
|
164
183
|
} else if(obj instanceof Set){
|
|
165
184
|
pre = 'Set(['
|
|
166
185
|
post = '])'
|
|
167
186
|
elems = [
|
|
168
|
-
_serialize([...obj], path, seen, indent, depth,
|
|
187
|
+
_serialize([...obj], path, seen, indent, depth, options)
|
|
169
188
|
.slice(1, -1)
|
|
170
189
|
.trim() ]
|
|
171
190
|
} else {
|
|
@@ -175,7 +194,7 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
175
194
|
elems.push(`${
|
|
176
195
|
JSON.stringify(k)
|
|
177
196
|
}:${ indent != null ? ' ' : '' }${
|
|
178
|
-
_serialize(v, [...path, k], seen, indent, depth+1,
|
|
197
|
+
_serialize(v, [...path, k], seen, indent, depth+1, options)
|
|
179
198
|
// relevant for pretty-printing only...
|
|
180
199
|
.trimLeft()
|
|
181
200
|
}`) } }
|
|
@@ -196,8 +215,8 @@ function(obj, path=[], seen=new Map(), indent, depth=0, functions){
|
|
|
196
215
|
// user interface...
|
|
197
216
|
var serialize =
|
|
198
217
|
module.serialize =
|
|
199
|
-
function(obj, indent, depth=0,
|
|
200
|
-
return _serialize(obj, [], new Map(), indent, depth,
|
|
218
|
+
function(obj, indent, depth=0, options){
|
|
219
|
+
return _serialize(obj, [], new Map(), indent, depth, options) }
|
|
201
220
|
|
|
202
221
|
|
|
203
222
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
@@ -357,6 +376,9 @@ module.eJSON = {
|
|
|
357
376
|
|| (str[j] >= '0'
|
|
358
377
|
&& str[j] <= '9'))){
|
|
359
378
|
j++ }
|
|
379
|
+
// BigInt...
|
|
380
|
+
if(str[j] == 'n'){
|
|
381
|
+
return [ BigInt(str.slice(i, j)), j+1, line ] }
|
|
360
382
|
return [ str.slice(i, j)*1, j, line ] },
|
|
361
383
|
// XXX TEST count \\n
|
|
362
384
|
string: function(state, path, match, str, i, line){
|
|
@@ -381,7 +403,10 @@ module.eJSON = {
|
|
|
381
403
|
if(j == str.length
|
|
382
404
|
&& str[j-1] != match){
|
|
383
405
|
this.error('Unexpected end of input wile looking fot "'+ match +'".', str, i, line) }
|
|
384
|
-
|
|
406
|
+
// NOTE: this is cheating -- instead of dancing around and
|
|
407
|
+
// replicating the full functionality of JSON.parse(..) we
|
|
408
|
+
// are simply falling back on it, as we did when serializing.
|
|
409
|
+
return [ JSON.parse(`"${str.slice(i+1, j)}"`), j+1, line ] },
|
|
385
410
|
identifier: function(state, path, match, str, i, line){
|
|
386
411
|
debug.lex('identifier', str, i, line)
|
|
387
412
|
if(!/[a-zA-Z_]/.test(str[i])){
|
|
@@ -554,10 +579,13 @@ module.eJSON = {
|
|
|
554
579
|
this.error('Expected "," got "'+ str[i] +'"', str, i, line) }
|
|
555
580
|
i++
|
|
556
581
|
|
|
557
|
-
// func
|
|
582
|
+
// func index...
|
|
558
583
|
if(state.functions instanceof Array){
|
|
559
584
|
var [n, i, line] = this.number(state, path, str[i+1], str, i+1, line)
|
|
560
585
|
res = state.functions[n]
|
|
586
|
+
if(str[i] != ')'){
|
|
587
|
+
this.error('Expected ")" got "'+ str[i] +'"', str, i, line) }
|
|
588
|
+
i++
|
|
561
589
|
// func code...
|
|
562
590
|
} else {
|
|
563
591
|
var code = str.slice(i, i+l)
|
|
@@ -600,10 +628,10 @@ module.eJSON = {
|
|
|
600
628
|
return this[handler](state, path, match, str, i, line) },
|
|
601
629
|
|
|
602
630
|
|
|
603
|
-
parse: function(str,
|
|
631
|
+
parse: function(str, options={}){
|
|
604
632
|
|
|
605
633
|
// stage 1: build the object...
|
|
606
|
-
var state = {functions}
|
|
634
|
+
var state = {functions: options.functions}
|
|
607
635
|
var res = this.value(state, [], str)[0]
|
|
608
636
|
|
|
609
637
|
// stage 2: link the recursive structures...
|
|
@@ -611,13 +639,20 @@ module.eJSON = {
|
|
|
611
639
|
this.setItem(res, a, this.getItem(res, b)) }
|
|
612
640
|
|
|
613
641
|
return res },
|
|
642
|
+
|
|
643
|
+
// to comply with POLS...
|
|
644
|
+
stringify: serialize,
|
|
614
645
|
}
|
|
615
646
|
|
|
616
647
|
|
|
617
648
|
var deserialize =
|
|
618
649
|
module.deserialize =
|
|
619
|
-
function(str,
|
|
620
|
-
|
|
650
|
+
function(str, options){
|
|
651
|
+
options =
|
|
652
|
+
options === true ?
|
|
653
|
+
{functions: true}
|
|
654
|
+
: options
|
|
655
|
+
return eJSON.parse(str, options) }
|
|
621
656
|
|
|
622
657
|
|
|
623
658
|
|
|
@@ -626,18 +661,20 @@ function(str, functions){
|
|
|
626
661
|
|
|
627
662
|
var deepCopy =
|
|
628
663
|
module.deepCopy =
|
|
629
|
-
function(obj){
|
|
664
|
+
function(obj, funcs){
|
|
665
|
+
var options = {functions: funcs}
|
|
630
666
|
return deserialize(
|
|
631
|
-
serialize(obj)
|
|
667
|
+
serialize(obj, null, 0, options),
|
|
668
|
+
options) }
|
|
632
669
|
|
|
633
670
|
|
|
634
|
-
var
|
|
635
|
-
module.
|
|
636
|
-
function(obj){
|
|
637
|
-
var
|
|
671
|
+
var partialDeepCopy =
|
|
672
|
+
module.partialDeepCopy =
|
|
673
|
+
function(obj, funcs=[]){
|
|
674
|
+
var options = {functions: funcs}
|
|
638
675
|
return deserialize(
|
|
639
|
-
serialize(obj, null, 0,
|
|
640
|
-
|
|
676
|
+
serialize(obj, null, 0, options),
|
|
677
|
+
options) }
|
|
641
678
|
|
|
642
679
|
|
|
643
680
|
|
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){
|
|
@@ -49,11 +51,13 @@ var setups = test.Setups({
|
|
|
49
51
|
return ['0.123', json] },
|
|
50
52
|
'float-b': function(assert){
|
|
51
53
|
return ['1.23', json] },
|
|
54
|
+
'bigint': function(assert){
|
|
55
|
+
return ['999999999999999999999n'] },
|
|
52
56
|
// XXX need a way to test this...
|
|
53
57
|
//'float-a': function(assert){
|
|
54
|
-
// return '.123' },
|
|
58
|
+
// return ['.123'] },
|
|
55
59
|
//'float-c': function(assert){
|
|
56
|
-
// return '123.' },
|
|
60
|
+
// return ['123.'] },
|
|
57
61
|
// XXX also test:
|
|
58
62
|
// hex/bin/orc/...
|
|
59
63
|
Infinity: function(assert){
|
|
@@ -64,6 +68,9 @@ var setups = test.Setups({
|
|
|
64
68
|
// XXX also test diffrerent quotations...
|
|
65
69
|
string: function(assert){
|
|
66
70
|
return ['"string"', json] },
|
|
71
|
+
// XXX ERR this breaks...
|
|
72
|
+
'string-multiline': function(assert){
|
|
73
|
+
return ['"string\\nstring\\nstring"', json] },
|
|
67
74
|
|
|
68
75
|
'array-empty': function(assert){
|
|
69
76
|
return ['[]', json] },
|
|
@@ -152,6 +159,71 @@ test.Tests({
|
|
|
152
159
|
var obj = eJSON.deserialize(setup, true)
|
|
153
160
|
assert(
|
|
154
161
|
JSON.stringify(obj) == eJSON.serialize(obj) ) },
|
|
162
|
+
|
|
163
|
+
'deep-copy': function(assert, [setup]){
|
|
164
|
+
var obj = eJSON.deserialize(setup, true)
|
|
165
|
+
var copy = eJSON.deepCopy(obj, true)
|
|
166
|
+
|
|
167
|
+
assert(eJSON.serialize(obj) == eJSON.serialize(copy))
|
|
168
|
+
|
|
169
|
+
// XXX check if all non-atoms are distinct...
|
|
170
|
+
// XXX
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
//* XXX ERR
|
|
174
|
+
'partial-deep-copy': function(assert, [setup]){
|
|
175
|
+
var obj = eJSON.deserialize(setup, true)
|
|
176
|
+
var funcs = []
|
|
177
|
+
var copy = eJSON.partialDeepCopy(obj, funcs)
|
|
178
|
+
|
|
179
|
+
assert(eJSON.serialize(obj) == eJSON.serialize(copy))
|
|
180
|
+
|
|
181
|
+
// XXX check if all non-atoms are distinct and functions are the same...
|
|
182
|
+
// XXX
|
|
183
|
+
},
|
|
184
|
+
//*/
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test.Cases({
|
|
188
|
+
/* XXX for some magical reason hese break as soon as we add [setup] to arguments...
|
|
189
|
+
'deep-copy-function': function(assert, [setup]){
|
|
190
|
+
// XXX check function isolation...
|
|
191
|
+
},
|
|
192
|
+
//*/
|
|
193
|
+
|
|
194
|
+
// NOTE: these syntax variants are not output by .serialize(..) this it
|
|
195
|
+
// is less critical to test them in the main loop.
|
|
196
|
+
// XXX though it would be nice to do so...
|
|
197
|
+
tests: [
|
|
198
|
+
// numbers/floats...
|
|
199
|
+
['.123', '0.123'],
|
|
200
|
+
['123.', '123'],
|
|
201
|
+
|
|
202
|
+
// string quotes...
|
|
203
|
+
['"abc"', "'abc'"],
|
|
204
|
+
['"abc"', '`abc`'],
|
|
205
|
+
|
|
206
|
+
// arrays...
|
|
207
|
+
['[1,2,]', '[1,2]'],
|
|
208
|
+
|
|
209
|
+
// sparse arrays...
|
|
210
|
+
['[<empty>]', '[,]'],
|
|
211
|
+
['[1,2,<empty>]', '[1,2,,]'],
|
|
212
|
+
['[1,2,<empty>]', '[1,2,<empty>,]'],
|
|
213
|
+
],
|
|
214
|
+
'syntax-simplifications': function(assert){
|
|
215
|
+
var aa, bb
|
|
216
|
+
for(var [a, b] of this.tests){
|
|
217
|
+
assert(eJSON.serialize(aa = eJSON.deserialize(a)) == eJSON.serialize(bb = eJSON.deserialize(b)),
|
|
218
|
+
`"${ a }" and "${ b }" should deserialize to the samve value.`,
|
|
219
|
+
'got:', aa, 'and', bb, 'resp.') } },
|
|
220
|
+
|
|
221
|
+
'deep-copy-function': function(assert){
|
|
222
|
+
// XXX check function isolation...
|
|
223
|
+
},
|
|
224
|
+
'partial-deep-copy-function': function(assert){
|
|
225
|
+
// XXX check function isolation...
|
|
226
|
+
},
|
|
155
227
|
})
|
|
156
228
|
|
|
157
229
|
|
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 })
|