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 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
@@ -0,0 +1,2 @@
1
+ # serilize.js
2
+ Extended JSON serilization
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 })