ig-serialize 1.0.0
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/LICENSE +29 -0
- package/README.md +2 -0
- package/package.json +29 -0
- package/serialize.js +645 -0
- package/serialize.js.bak +199 -0
- package/serialize2.js +645 -0
- package/test.js +168 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-, Alex A. Naanou
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ig-serialize",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "experimental extended json serializaion...",
|
|
5
|
+
"main": "serialize.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "c8 node ./test.js -m 2",
|
|
8
|
+
"cover": "c8 -r text -r lcov node ./test.js -m 2",
|
|
9
|
+
"prepublishOnly": "npm test"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/flynx/serialize.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"JSON",
|
|
17
|
+
"serialization"
|
|
18
|
+
],
|
|
19
|
+
"author": "Alex A. Naanou <alex.nanou@gmail.com>",
|
|
20
|
+
"license": "BSD-3-Clause",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/flynx/serialize.js/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/flynx/serialize.js#readme",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"c8": "^7.4.0",
|
|
27
|
+
"ig-test": "^1.6.*"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/serialize.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
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 })
|