ig-serialize 1.0.1 → 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 CHANGED
@@ -11,5 +11,44 @@ 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
+
14
53
 
15
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ig-serialize",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "experimental extended json serializaion...",
5
5
  "main": "serialize.js",
6
6
  "scripts": {
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[, indent[, depth]])
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, functions)
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, functions){
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 instanceof Array){
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, functions)
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, functions)
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, functions)
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, functions)
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, functions){
200
- return _serialize(obj, [], new Map(), indent, depth, functions) }
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
- return [ str.slice(i+1, j), j+1, line ] },
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])){
@@ -543,7 +568,6 @@ module.eJSON = {
543
568
  // NOTE: this uses eval(..) so care must be taken when enabling this...
544
569
  func: function(state, path, match, str, i, line){
545
570
  if(state.functions == null){
546
- console.log('---', state)
547
571
  this.error('Deserializing functions disabled.', str, i, line) }
548
572
 
549
573
  debug.lex('function', str, i, line)
@@ -555,7 +579,7 @@ module.eJSON = {
555
579
  this.error('Expected "," got "'+ str[i] +'"', str, i, line) }
556
580
  i++
557
581
 
558
- // func ref...
582
+ // func index...
559
583
  if(state.functions instanceof Array){
560
584
  var [n, i, line] = this.number(state, path, str[i+1], str, i+1, line)
561
585
  res = state.functions[n]
@@ -604,10 +628,10 @@ module.eJSON = {
604
628
  return this[handler](state, path, match, str, i, line) },
605
629
 
606
630
 
607
- parse: function(str, functions){
631
+ parse: function(str, options={}){
608
632
 
609
633
  // stage 1: build the object...
610
- var state = {functions}
634
+ var state = {functions: options.functions}
611
635
  var res = this.value(state, [], str)[0]
612
636
 
613
637
  // stage 2: link the recursive structures...
@@ -615,13 +639,20 @@ module.eJSON = {
615
639
  this.setItem(res, a, this.getItem(res, b)) }
616
640
 
617
641
  return res },
642
+
643
+ // to comply with POLS...
644
+ stringify: serialize,
618
645
  }
619
646
 
620
647
 
621
648
  var deserialize =
622
649
  module.deserialize =
623
- function(str, functions){
624
- return eJSON.parse(str, functions) }
650
+ function(str, options){
651
+ options =
652
+ options === true ?
653
+ {functions: true}
654
+ : options
655
+ return eJSON.parse(str, options) }
625
656
 
626
657
 
627
658
 
@@ -630,18 +661,20 @@ function(str, functions){
630
661
 
631
662
  var deepCopy =
632
663
  module.deepCopy =
633
- function(obj, functions){
664
+ function(obj, funcs){
665
+ var options = {functions: funcs}
634
666
  return deserialize(
635
- serialize(obj, null, 0, functions),
636
- functions) }
667
+ serialize(obj, null, 0, options),
668
+ options) }
637
669
 
638
670
 
639
671
  var partialDeepCopy =
640
672
  module.partialDeepCopy =
641
673
  function(obj, funcs=[]){
674
+ var options = {functions: funcs}
642
675
  return deserialize(
643
- serialize(obj, null, 0, funcs),
644
- funcs) }
676
+ serialize(obj, null, 0, options),
677
+ options) }
645
678
 
646
679
 
647
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] },
@@ -177,17 +184,47 @@ test.Tests({
177
184
  //*/
178
185
  })
179
186
 
180
- /* XXX these break things...
181
187
  test.Cases({
188
+ /* XXX for some magical reason hese break as soon as we add [setup] to arguments...
182
189
  'deep-copy-function': function(assert, [setup]){
183
190
  // XXX check function isolation...
184
191
  },
192
+ //*/
185
193
 
186
- 'partial-deep-copy-function': function(assert, [setup]){
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){
187
225
  // XXX check function isolation...
188
226
  },
189
227
  })
190
- //*/
191
228
 
192
229
 
193
230
  //---------------------------------------------------------------------
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 })