@muze-nl/od-jsontag 0.3.3 → 0.4.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/src/parse.mjs CHANGED
@@ -3,11 +3,11 @@ import Null from '@muze-nl/jsontag/src/lib/Null.mjs'
3
3
  import serialize from './serialize.mjs'
4
4
  import {source,isProxy,proxyType,getBuffer,getIndex,isChanged,isParsed,position,parent,resultSet} from './symbols.mjs'
5
5
 
6
- const decoder = new TextDecoder()
7
6
  const encoder = new TextEncoder()
8
- const arrayProxies = new WeakMap()
7
+ const decoder = new TextDecoder()
9
8
 
10
- function stringToSAB(strData) {
9
+ function stringToSAB(strData)
10
+ {
11
11
  const buffer = encoder.encode(strData)
12
12
  const sab = new SharedArrayBuffer(buffer.length)
13
13
  let uint8sab = new Uint8Array(sab)
@@ -15,7 +15,8 @@ function stringToSAB(strData) {
15
15
  return uint8sab
16
16
  }
17
17
 
18
- function SABtoString(arr) {
18
+ function SABtoString(arr)
19
+ {
19
20
  let string = '';
20
21
  for (let c of arr) {
21
22
  string+= String.fromCharCode(c)
@@ -23,612 +24,421 @@ function SABtoString(arr) {
23
24
  return string
24
25
  }
25
26
 
26
- class Slice {
27
- constructor(start, end) {
27
+ class Slice
28
+ {
29
+ constructor(start, end)
30
+ {
28
31
  this.start = start;
29
32
  this.end = end;
30
33
  }
31
34
  }
32
35
 
33
- const isSlice = function(r) {
36
+ const isSlice = function(r)
37
+ {
34
38
  return r instanceof Slice
35
39
  }
36
40
 
37
- export default function parse(input, meta, immutable=true)
41
+ const resetObject = function(ob)
38
42
  {
39
- if (!meta) {
40
- meta = {}
41
- }
42
- if (!meta.unresolved) {
43
- meta.unresolved = new Map()
44
- }
45
- if (!meta.baseURL) {
46
- meta.baseURL = 'http://localhost/'
47
- }
48
- let at, ch, value, result;
49
- let escapee = {
50
- '"': '"',
51
- "\\":"\\",
52
- '/': '/',
53
- b: "\b",
54
- f: "\f",
55
- n: "\n",
56
- r: "\r",
57
- t: "\t"
58
- }
59
- let offsetArray = []
60
- if (!meta.resultArray) {
61
- meta.resultArray = []
62
- }
63
-
64
- at = 0
65
- ch = " "
66
-
67
- let error = function(m)
68
- {
69
- let context
70
- try {
71
- context = decoder.decode(input.slice(at-100,at+100));
72
- } catch(err) {}
73
- throw {
74
- name: 'SyntaxError',
75
- message: m,
76
- at: at,
77
- input: context
78
- }
79
- }
80
-
81
- if (typeof input == 'string' || input instanceof String) {
82
- input = stringToSAB(input)
83
- }
84
- if (!(input instanceof Uint8Array)) {
85
- error('parse only accepts Uint8Array or String as input')
86
- }
87
-
88
- let next = function(c)
89
- {
90
- if (c && c!==ch) {
91
- let source = SABtoString(input)
92
- error("Expected '"+c+"' instead of '"+ch+"': "+at+':'+source)
93
- }
94
- ch = String.fromCharCode(input.at(at))
95
- at+=1
96
- return ch
97
- }
98
-
99
- let number = function(tagName)
100
- {
101
- let numString = ''
102
- if (ch==='-') {
103
- numString = '-'
104
- next('-')
105
- }
106
- while(ch>='0' && ch<='9') {
107
- numString += ch
108
- next()
109
- }
110
- if (ch==='.') {
111
- numString+='.'
112
- while(next() && ch >= '0' && ch <= '9') {
113
- numString += ch
114
- }
115
- }
116
- if (ch === 'e' || ch === 'E') {
117
- numString += ch
118
- next()
119
- if (ch === '-' || ch === '+') {
120
- numString += ch
121
- next()
122
- }
123
- while (ch >= '0' && ch <= '9') {
124
- numString += ch
125
- next()
126
- }
127
- }
128
- let result = new Number(numString).valueOf()
129
- if (tagName) {
130
- switch(tagName) {
131
- case "int":
132
- isInt(numString)
133
- break
134
- case "uint":
135
- isInt(numString, [0,Infinity])
136
- break
137
- case "int8":
138
- isInt(numString, [-128,127])
139
- break
140
- case "uint8":
141
- isInt(numString, [0,255])
142
- break
143
- case "int16":
144
- isInt(numString, [-32768,32767])
145
- break
146
- case "uint16":
147
- isInt(numString, [0,65535])
148
- break
149
- case "int32":
150
- isInt(numString, [-2147483648, 2147483647])
151
- break
152
- case "uint32":
153
- isInt(numString, [0,4294967295])
154
- break
155
- case "timestamp":
156
- case "int64":
157
- isInt(numString, [-9223372036854775808,9223372036854775807])
158
- break
159
- case "uint64":
160
- isInt(numString, [0,18446744073709551615])
161
- break
162
- case "float":
163
- isFloat(numString)
164
- break
165
- case "float32":
166
- isFloat(numString, [-3.4e+38,3.4e+38])
167
- break
168
- case "float64":
169
- isFloat(numString, [-1.7e+308,+1.7e+308])
170
- break
171
- case "number":
172
- //FIXME: what to check? should already be covered by JSON parsing rules?
173
- break
174
- default:
175
- isTypeError(tagName, numString)
176
- break
177
- }
178
- }
179
- return result
43
+ delete ob[Symbol['JSONTag:Type']]
44
+ delete ob[Symbol['JSONTag:Attributes']]
45
+ for (let prop of Object.getOwnPropertyNames(ob)) {
46
+ delete ob[prop]
180
47
  }
48
+ }
181
49
 
182
- let isTypeError = function(type, value)
183
- {
184
- error('Syntax error, expected '+type+', got: '+value)
185
- }
50
+ export default class Parser extends JSONTag.Parser
51
+ {
186
52
 
187
- const regexes = {
188
- color: /^(rgb|hsl)a?\((\d+%?(deg|rad|grad|turn)?[,\s]+){2,3}[\s\/]*[\d\.]+%?\)$/i,
189
- email: /^[A-Za-z0-9_!#$%&'*+\/=?`{|}~^.-]+@[A-Za-z0-9.-]+$/,
190
- uuid: /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
191
- decimal: /^\d*\.?\d*$/,
192
- money: /^[A-Z]+\$\d*\.?\d*$/,
193
- duration: /^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/,
194
- phone: /^[+]?(?:\(\d+(?:\.\d+)?\)|\d+(?:\.\d+)?)(?:[ -]?(?:\(\d+(?:\.\d+)?\)|\d+(?:\.\d+)?))*(?:[ ]?(?:x|ext)\.?[ ]?\d{1,5})?$/,
195
- time: /^(\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?$/,
196
- date: /^-?[1-9][0-9]{3,}-([0][1-9]|[1][0-2])-([1-2][0-9]|[0][1-9]|[3][0-1])$/,
197
- datetime: /^(\d{4,})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?$/,
198
- range: /^\[-?(\d+\.)?\d+\,-?(\d+\.)?\d+\]$/
199
- }
53
+ handlers
200
54
 
201
- let isFloat = function(float, range)
55
+ constructor(baseURL, immutable=true)
202
56
  {
203
- let test = new Number(parseFloat(float))
204
- let str = test.toString()
205
- if (float!==str) {
206
- error('Syntax Error: expected float value')
207
- }
208
- if (range) {
209
- if (typeof range[0] === 'number') {
210
- if (test<range[0]) {
211
- error('Syntax Error: float value out of range')
212
- }
213
- }
214
- if (typeof range[1] === 'number') {
215
- if (test>range[1]) {
216
- error('Syntax Error: float value out of range')
57
+ super(baseURL)
58
+ this.cachedProxies = new Map() //FIXME: set back to WeakMap
59
+ this.immutable = immutable
60
+ this.handlers = {
61
+ newArrayHandler: {
62
+ get: (target, prop) => {
63
+ if (target[prop] instanceof Function) {
64
+ return (...args) => {
65
+ args = args.map(arg => {
66
+ if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
67
+ arg = this.getNewValueProxy(arg)
68
+ }
69
+ return arg
70
+ })
71
+ return target[prop].apply(target, args)
72
+ }
73
+ } else if (prop===isChanged) {
74
+ return true
75
+ } else {
76
+ if (this.meta.access && !this.meta.access(target, prop)) {
77
+ return undefined
78
+ }
79
+ if (Array.isArray(target[prop])) {
80
+ return this.getArrayProxy(target[prop], target, this.handlers.newArrayHandler)
81
+ }
82
+ return target[prop]
83
+ }
84
+ },
85
+ set: (target, prop, value) => {
86
+ if (prop === isChanged || prop === parent) {
87
+ // prevent infinite loops, parent is only needed to mark it isChanged
88
+ // but this is a new array proxy, parent is already dirty
89
+ return true
90
+ }
91
+ if (this.meta.access && !this.meta.access(target, prop)) {
92
+ return undefined
93
+ }
94
+ if (JSONTag.getType(value)==='object' && !value[isProxy]) {
95
+ value = this.getNewValueProxy(value)
96
+ }
97
+ target[prop] = value
98
+ return true
217
99
  }
218
- }
219
- }
220
- }
221
-
222
- let isInt = function(int, range)
223
- {
224
- let test = new Number(parseInt(int))
225
- let str = test.toString()
226
- if (int!==str) {
227
- error('Syntax Error: expected integer value')
228
- }
229
- if (range) {
230
- if (typeof range[0] === 'number') {
231
- if (test<range[0]) {
232
- error('Syntax Error: integer value out of range')
100
+ },
101
+ newValueHandler: {
102
+ get: (target, prop) => {
103
+ switch(prop) {
104
+ case resultSet:
105
+ return this.meta.resultArray
106
+ break;
107
+ case source:
108
+ return target
109
+ break
110
+ case isProxy:
111
+ return true
112
+ break
113
+ case proxyType:
114
+ return 'new'
115
+ break
116
+ case getBuffer:
117
+ return (i) => {
118
+ let index = target[getIndex]
119
+ if (i != index) {
120
+ return encoder.encode('~'+index)
121
+ }
122
+ return serialize(target, {meta:this.meta, skipLength:true})
123
+ }
124
+ break
125
+ case getIndex:
126
+ return target[getIndex]
127
+ break
128
+ case isChanged:
129
+ return true
130
+ break
131
+ default:
132
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
133
+ return undefined
134
+ }
135
+ if (Array.isArray(target[prop])) {
136
+ return this.getArrayProxy(target[prop], target, this.handlers.newArrayHandler)
137
+ }
138
+ return target[prop]
139
+ break
140
+ }
141
+ },
142
+ set: (target, prop, value) => {
143
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
144
+ return undefined
145
+ }
146
+ if (JSONTag.getType(value)==='object' && !value[isProxy]) {
147
+ value = this.getNewValueProxy(value)
148
+ }
149
+ target[prop] = value
150
+ return true
233
151
  }
234
- }
235
- if (typeof range[1] === 'number') {
236
- if (test>range[1]) {
237
- error('Syntax Error: integer value out of range')
152
+ },
153
+ arrayHandler: {
154
+ get: (target, prop, receiver) => {
155
+ const value = target?.[prop]
156
+ if (value instanceof Function) {
157
+ // if (['copyWithin','fill','pop','push','reverse','shift','sort','splice','unshift'].indexOf(prop)!==-1) {
158
+ // if (immutable) {
159
+ // throw new Error('dataspace is immutable')
160
+ // }
161
+ // }
162
+ return (...args) => {
163
+ args = args.map(arg => {
164
+ if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
165
+ arg = this.getNewValueProxy(arg)
166
+ }
167
+ return arg
168
+ })
169
+ return value.apply(receiver, args)
170
+ }
171
+ } else if (prop===isChanged) {
172
+ return target[isChanged] || target[parent][isChanged]
173
+ } else if (prop===source) {
174
+ return target
175
+ } else {
176
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
177
+ return undefined
178
+ }
179
+ if (Array.isArray(value)) {
180
+ return this.getArrayProxy(value, target)
181
+ }
182
+ return value
183
+ }
184
+ },
185
+ set: (target, prop, value) => {
186
+ if (prop == parent) {
187
+ target[parent] = value
188
+ return true
189
+ }
190
+ if (this.immutable) {
191
+ throw new Error('dataspace is immutable')
192
+ }
193
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
194
+ return undefined
195
+ }
196
+ if (JSONTag.getType(value)==='object' && !value[isProxy]) {
197
+ value = this.getNewValueProxy(value)
198
+ }
199
+ if (target[prop] === value) {
200
+ return true
201
+ }
202
+ target[prop] = value
203
+ target[isChanged] = true
204
+ target[parent][isChanged] = true
205
+ return true
206
+ },
207
+ deleteProperty: (target, prop) => {
208
+ if (this.immutable) {
209
+ throw new Error('dataspace is immutable')
210
+ }
211
+ if (this.meta.access && !this.meta.access(target, prop, 'deleteProperty')) {
212
+ return undefined
213
+ }
214
+ //FIXME: if target[prop] was the last reference to an object
215
+ //that object should be deleted so that its line will become empty
216
+ //when stringifying resultArray again
217
+ if (typeof target[prop] === 'undefined') {
218
+ return true
219
+ }
220
+ delete target[prop]
221
+ target[isChanged] = true
222
+ target[parent][isChanged] = true
223
+ return true
238
224
  }
239
- }
240
- }
241
- }
242
-
243
- let isColor = function(color)
244
- {
245
- let result = false
246
- if (color.charAt(0) === "#") {
247
- color = color.substring(1)
248
- result = ([3, 4, 6, 8].indexOf(color.length) > -1) && !isNaN(parseInt(color, 16))
249
- if (result.toString(16)!==color) {
250
- isTypeError('color', color)
251
- }
252
- } else {
253
- result = regexes.color.test(color)
254
- }
255
- if (!result) {
256
- isTypeError('color',color)
257
- }
258
- return true
259
- }
260
-
261
- let isEmail = function(email)
262
- {
263
- let result = regexes.email.test(email)
264
- if (!result) {
265
- isTypeError('email',email)
266
- }
267
- return true
268
- }
269
-
270
- let isUuid = function(uuid)
271
- {
272
- let result = regexes.uuid.test(uuid)
273
- if (!result) {
274
- isTypeError('uuid',uuid)
275
- }
276
- return true
277
- }
278
-
279
- let isDecimal = function(decimal)
280
- {
281
- let result = regexes.decimal.test(decimal)
282
- if (!result) {
283
- isTypeError('decimal',decimal)
284
- }
285
- return true
286
- }
287
-
288
- let isMoney = function(money)
289
- {
290
- let result = regexes.money.test(money)
291
- if (!result) {
292
- isTypeError('money',money)
293
- }
294
- return true
295
- }
296
-
297
- let isUrl = function(url)
298
- {
299
- try {
300
- return Boolean(new URL(url, meta.baseURL))
301
- } catch(e) {
302
- isTypeError('url',url)
303
- }
304
- }
305
-
306
- let isDuration = function(duration)
307
- {
308
- let result = regexes.duration.test(duration)
309
- if (!result) {
310
- isTypeError('duration',duration)
311
- }
312
- return true
313
- }
314
-
315
- let isPhone = function(phone)
316
- {
317
- let result = regexes.phone.test(phone)
318
- if (!result) {
319
- isTypeError('phone',phone)
320
- }
321
- return true
322
- }
323
-
324
- let isRange = function(range)
325
- {
326
- let result = regexes.range.test(range)
327
- if (!result) {
328
- isTypeError('range',range)
329
- }
330
- return true
331
- }
332
-
333
- let isTime = function(time)
334
- {
335
- let result = regexes.time.test(time)
336
- if (!result) {
337
- isTypeError('time',time)
338
- }
339
- return true
340
- }
341
-
342
- let isDate = function(date)
343
- {
344
- let result = regexes.date.test(date)
345
- if (!result) {
346
- isTypeError('date',date)
347
- }
348
- return true
349
- }
350
-
351
- let isDatetime = function(datetime)
352
- {
353
- let result = regexes.datetime.test(datetime)
354
- if (!result) {
355
- isTypeError('datetime',datetime)
356
- }
357
- return true
358
- }
359
-
360
- let checkStringType = function(tagName, value)
361
- {
362
- if (!tagName) {
363
- return
364
- }
365
- switch(tagName){
366
- case "object":
367
- case "array":
368
- case "int8":
369
- case "uint8":
370
- case "int16":
371
- case "uint16":
372
- case "int32":
373
- case "uint32":
374
- case "int64":
375
- case "uint64":
376
- case "int":
377
- case "uint":
378
- case "float32":
379
- case "float64":
380
- case "float":
381
- case "timestamp":
382
- isTypeError(tagName, value)
383
- break
384
- case "uuid":
385
- return isUuid(value)
386
- case "decimal":
387
- return isDecimal(value)
388
- case "money":
389
- return isMoney(value)
390
- case "url":
391
- return isUrl(value)
392
- case "link":
393
- case "string":
394
- case "text":
395
- case "blob":
396
- case "hash":
397
- //anything goes
398
- return true
399
- case "color":
400
- return isColor(value)
401
- case "email":
402
- return isEmail(value)
403
- case "duration":
404
- return isDuration(value)
405
- case "phone":
406
- return isPhone(value)
407
- case "range":
408
- return isRange(value)
409
- case "time":
410
- return isTime(value)
411
- case "date":
412
- return isDate(value)
413
- case "datetime":
414
- return isDatetime(value)
415
- }
416
- error('Syntax error: unknown tagName '+tagName)
417
- }
418
-
419
- let string = function(tagName)
420
- {
421
- let value = [], hex, i, uffff;
422
- if (ch !== '"') {
423
- error("Syntax Error")
424
- }
425
- next('"')
426
- while(ch) {
427
- if (ch==='"') {
428
- next()
429
- let bytes = new Uint8Array(value)
430
- value = decoder.decode(bytes)
431
- checkStringType(tagName, value)
432
- return value
433
- }
434
- if (ch==='\\') {
435
- next()
436
- if (ch==='u') {
437
- for (i=0; i<4; i++) {
438
- hex = parseInt(next(), 16)
439
- if (!isFinite(hex)) {
225
+ },
226
+ defaultHandler: {
227
+ get: (target, prop, receiver) => {
228
+ switch(prop) {
229
+ case resultSet:
230
+ return this.meta.resultArray
231
+ break;
232
+ case isProxy:
233
+ return true
234
+ break
235
+ case proxyType:
236
+ return 'parse'
237
+ break
238
+ case getBuffer:
239
+ return (i) => {
240
+ let index = target[getIndex]
241
+ if (i != index) {
242
+ return encoder.encode('~'+index)
243
+ }
244
+ if (target[isChanged]) {
245
+ return serialize(target, {skipLength: true})
246
+ }
247
+ return target[position].input.slice(target[position].start,target[position].end)
248
+ }
249
+ break
250
+ case getIndex:
251
+ return target[getIndex]
252
+ break
253
+ case isChanged:
254
+ return target[isChanged]
255
+ break
256
+ }
257
+ this.firstParse(target, receiver)
258
+ switch(prop) {
259
+ case source:
260
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
261
+ return undefined
262
+ }
263
+ return target
264
+ break
265
+ default:
266
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
267
+ return undefined
268
+ }
269
+ if (Array.isArray(target[prop])) {
270
+ return this.getArrayProxy(target[prop], target)
271
+ }
272
+ return target[prop]
273
+ break
274
+ }
275
+ },
276
+ set: (target, prop, value, receiver) => {
277
+ if (this.immutable && prop!==resultSet && prop!==source && prop!==isChanged) {
278
+ throw new Error('dataspace is immutable')
279
+ }
280
+ switch(prop) {
281
+ case isChanged:
282
+ break
283
+ case source:
284
+ resetObject(target)
285
+ target[position] = value[position]
286
+ target[isParsed] = false
287
+ target[isChanged] = false
288
+ return true
289
+ break
290
+ case resultSet:
440
291
  break
441
- }
442
- uffff = uffff * 16 + hex
443
292
  }
444
- let str = String.fromCharCode(uffff)
445
- let bytes = encoder.encode(str)
446
- value.push.apply(value, bytes)
447
- next()
448
- } else if (typeof escapee[ch] === 'string') {
449
- value.push(escapee[ch].charCodeAt(0))
450
- next()
451
- } else {
452
- break
293
+ this.firstParse(target, receiver)
294
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
295
+ return undefined
296
+ }
297
+ if (value && JSONTag.getType(value)==='object' && !value[isProxy]) {
298
+ value = this.getNewValueProxy(value)
299
+ }
300
+ if (target[prop] === value) {
301
+ return true
302
+ }
303
+ target[prop] = value
304
+ target[isChanged] = true
305
+ return true
306
+ },
307
+ deleteProperty: (target, prop) => {
308
+ if (this.immutable) {
309
+ throw new Error('dataspace is immutable')
310
+ }
311
+ if (this.meta.access && !this.meta.access(target, prop, 'deleteProperty')) {
312
+ return undefined
313
+ }
314
+ this.firstParse(target)
315
+ if (typeof target[prop] === 'undefined') {
316
+ return true
317
+ }
318
+ delete target[prop]
319
+ target[isChanged] = true
320
+ return true
321
+ },
322
+ ownKeys: (target) => {
323
+ this.firstParse(target)
324
+ return Reflect.ownKeys(target)
325
+ },
326
+ getOwnPropertyDescriptor: (target, prop) => {
327
+ this.firstParse(target)
328
+ return Reflect.getOwnPropertyDescriptor(target, prop)
329
+ },
330
+ defineProperty: (target, prop, descriptor) => {
331
+ if (this.immutable) {
332
+ throw new Error('dataspace is immutable')
333
+ }
334
+ if (this.meta.access && !this.meta.access(target, prop, 'defineProperty')) {
335
+ return undefined
336
+ }
337
+ this.firstParse(target)
338
+ target[isChanged] = true
339
+ return Object.defineProperty(target, prop, descriptor)
340
+ },
341
+ has: (target, prop) => {
342
+ if (this.meta.access && !this.meta.access(target, prop, 'has')) {
343
+ return false
344
+ }
345
+ this.firstParse()
346
+ return prop in target
347
+ },
348
+ setPrototypeOf: () => {
349
+ throw new Error('changing prototypes is not supported')
453
350
  }
454
- } else {
455
- value.push(ch.charCodeAt(0))
456
- next()
457
351
  }
458
352
  }
459
- error("Syntax error: incomplete string")
460
353
  }
461
354
 
462
- let tag = function()
355
+ next(c)
463
356
  {
464
- let key, val, tagOb={
465
- attributes: {}
466
- }
467
- if (ch !== '<') {
468
- error("Syntax Error")
469
- }
470
- next('<')
471
- key = word()
472
- if (!key) {
473
- error('Syntax Error: expected tag name')
474
- }
475
- tagOb.tagName = key
476
- whitespace()
477
- while(ch) {
478
- if (ch==='>') {
479
- next('>')
480
- return tagOb
481
- }
482
- key = word()
483
- if (!key) {
484
- error('Syntax Error: expected attribute name')
485
- }
486
- whitespace()
487
- next('=')
488
- whitespace()
489
- val = string()
490
- tagOb.attributes[key] = val
491
- whitespace()
492
- }
493
- error('Syntax Error: unexpected end of input')
494
- }
495
-
496
- let whitespace = function()
497
- {
498
- while (ch) {
499
- switch(ch) {
500
- case ' ':
501
- case "\t":
502
- case "\r":
503
- case "\n":
504
- next()
505
- break
506
- default:
507
- return
508
- break
509
- }
357
+ if (c && c!==this.ch) {
358
+ let source = SABtoString(this.input)
359
+ this.error("Expected '"+c+"' instead of '"+this.ch+"':"+this.at+':'+source)
510
360
  }
361
+ this.ch = String.fromCharCode(this.input.at(this.at))
362
+ this.at+=1
363
+ return this.ch
511
364
  }
512
365
 
513
- let word = function()
366
+ error(m)
514
367
  {
515
- //[a-z][a-z0-9_]*
516
- let val='';
517
- if ((ch>='a' && ch<='z') || (ch>='A' && ch<='Z')) {
518
- val += ch
519
- next()
520
- } else {
521
- error('Syntax Error: expected word')
522
- }
523
- while((ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || (ch>='0' && ch<='9') || ch=='_') {
524
- val += ch
525
- next()
526
- }
527
- return val
528
- }
368
+ let context
369
+ try {
370
+ context = decoder.decode(this.input.slice(this.at,this.at+100));
371
+ } catch(e) {
529
372
 
530
- let boolOrNull = function(tagName)
531
- {
532
- let w = word()
533
- if (!w || typeof w !== 'string') {
534
- error('Syntax error: expected boolean or null, got "'+w+'"')
535
- }
536
- switch(w.toLowerCase()) {
537
- case 'true':
538
- if (tagName && tagName!=='boolean') {
539
- isTypeError(tagName,w)
540
- }
541
- return true
542
- break
543
- case 'false':
544
- if (tagName && tagName!=='boolean') {
545
- isTypeError(tagName,w)
546
- }
547
- return false
548
- break
549
- case 'null':
550
- return null
551
- break
552
- default:
553
- error('Syntax error: expected boolean or null, got "'+w+'"')
554
- break
555
373
  }
556
- }
557
-
558
- let checkUnresolved = function(item, object, key)
559
- {
560
- if (JSONTag.getType(item)==='link') {
561
- let link = ''+item
562
- let links = meta.unresolved.get(link)
563
- if (typeof links === 'undefined') {
564
- meta.unresolved.set(link,[])
565
- links = meta.unresolved.get(link)
566
- }
567
- let count = links.push({
568
- src: new WeakRef(object),
569
- key: key
570
- })
374
+ throw {
375
+ name: 'SyntaxError',
376
+ message: m,
377
+ at: this.at,
378
+ input: context
571
379
  }
572
380
  }
573
381
 
574
- let array = function()
382
+ array()
575
383
  {
576
384
  let item, array = []
577
- if (ch !== '[') {
578
- error("Syntax error")
385
+ if (this.ch !== '[') {
386
+ this.error("Syntax error")
579
387
  }
580
- next('[')
581
- whitespace()
582
- if (ch===']') {
583
- next(']')
388
+ this.next('[')
389
+ this.whitespace()
390
+ if (this.ch===']') {
391
+ this.next(']')
584
392
  return array
585
393
  }
586
- while(ch) {
587
- item = value()
588
- checkUnresolved(item, array, array.length)
394
+ while(this.ch) {
395
+ item = this.value()
396
+ this.checkUnresolved(item, array, array.length)
589
397
  if (isSlice(item)) {
590
- array = array.concat(meta.resultArray.slice(item.start, item.end))
398
+ array = array.concat(this.meta.resultArray.slice(item.start, item.end))
591
399
  } else {
592
400
  array.push(item)
593
401
  }
594
- whitespace()
595
- if (ch===']') {
596
- next(']')
402
+ this.whitespace()
403
+ if (this.ch===']') {
404
+ this.next(']')
597
405
  return array
598
406
  }
599
- next(',')
600
- whitespace()
407
+ this.next(',')
408
+ this.whitespace()
601
409
  }
602
- error("Input stopped early")
410
+ this.error("Input stopped early")
603
411
  }
604
412
 
605
- let object = function(object={})
413
+
414
+ object(object={})
606
415
  {
607
416
  let key, val
608
- if (ch !== '{') {
609
- error("Syntax Error")
610
- }
611
- next('{')
612
- whitespace()
613
- if (ch==='}') {
614
- next('}')
417
+ if (this.ch !== '{') {
418
+ this.error("Syntax Error")
419
+ }
420
+ this.next('{')
421
+ this.whitespace()
422
+ resetObject(object)
423
+ if (this.ch==='}') {
424
+ this.next('}')
615
425
  return object
616
426
  }
617
427
  let enumerable = true
618
- while(ch) {
619
- if (ch==='#') {
428
+ while(this.ch) {
429
+ if (this.ch==='#') {
620
430
  enumerable = false
621
- next()
431
+ this.next()
622
432
  } else {
623
433
  enumerable = true
624
434
  }
625
- key = string()
435
+ key = this.string()
626
436
  if (key==='__proto__') {
627
- error("Attempt at prototype pollution")
437
+ this.error("Attempt at prototype pollution")
628
438
  }
629
- whitespace()
630
- next(':')
631
- val = value()
439
+ this.whitespace()
440
+ this.next(':')
441
+ val = this.value()
632
442
  if (!enumerable) {
633
443
  Object.defineProperty(object, key, {
634
444
  configurable: true, //important, must be true, otherwise Proxies cannot use it
@@ -639,476 +449,158 @@ export default function parse(input, meta, immutable=true)
639
449
  } else {
640
450
  object[key] = val
641
451
  }
642
- checkUnresolved(val, object, key)
643
- whitespace()
644
- if (ch==='}') {
645
- next('}')
452
+ this.checkUnresolved(val, object, key)
453
+ this.whitespace()
454
+ if (this.ch==='}') {
455
+ this.next('}')
646
456
  return object
647
457
  }
648
- next(',')
649
- whitespace()
650
- }
651
- error("Input stopped early")
652
- }
653
-
654
- let length = function()
655
- {
656
- whitespace()
657
- next('(')
658
- let numString=''
659
- while(ch>='0' && ch<='9') {
660
- numString += ch
661
- next()
458
+ this.next(',')
459
+ this.whitespace()
662
460
  }
663
- if (ch!==')') {
664
- error('Syntax error: not a length')
665
- }
666
- next()
667
- return parseInt(numString)
461
+ this.error("Input stopped early")
668
462
  }
669
463
 
670
- let offset = function()
464
+ string(tagName)
671
465
  {
672
- next('~')
673
- let numString = ''
674
- while(ch>='0' && ch<='9') {
675
- numString += ch
676
- next()
677
- }
678
- if (ch=='-') {
679
- next('-')
680
- let endString = ''
681
- while(ch>='0' && ch<='9') {
682
- endString += ch
683
- next()
684
- }
685
- return new Slice(parseInt(numString),parseInt(endString)+1) // +1 because array.slice(start,end) slices upto but not including end
686
- }
687
- return parseInt(numString)
688
- }
689
-
690
- let parseValue = function(position, ob={}) {
691
- input = position.input
692
- at = position.start
693
- next()
694
- return value(ob)
695
- }
696
-
697
- const makeChildProxies = function(parent) {
698
- Object.entries(parent).forEach(([key,entry]) => {
699
- if (Array.isArray(entry)) {
700
- makeChildProxies(entry)
701
- } else if (entry && JSONTag.getType(entry)==='object') {
702
- if (entry[isProxy]) {
703
- // do nothing
704
- } else {
705
- parent[key] = getNewValueProxy(entry)
706
- }
707
- }
708
- })
709
- }
710
-
711
- const getArrayProxy = (arr, par, handler) => {
712
- if (!handler) {
713
- handler = handlers.arrayHandler
714
- }
715
- if (!arrayProxies.has(arr)) {
716
- arrayProxies.set(arr, new Proxy(arr, handler))
466
+ let value = [], hex, i, uffff;
467
+ if (this.ch !== '"') {
468
+ this.error("Syntax Error")
717
469
  }
718
- let aProxy = arrayProxies.get(arr)
719
- aProxy[parent] = par
720
- return aProxy
721
- }
722
-
723
- const handlers = {
724
- newArrayHandler: {
725
- get(target, prop) {
726
- if (target[prop] instanceof Function) {
727
- return (...args) => {
728
- args = args.map(arg => {
729
- if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
730
- arg = getNewValueProxy(arg)
731
- }
732
- return arg
733
- })
734
- return target[prop].apply(target, args)
735
- }
736
- } else if (prop===isChanged) {
737
- return true
738
- } else {
739
- if (meta.access && !meta.access(target, prop)) {
740
- return undefined
741
- }
742
- if (Array.isArray(target[prop])) {
743
- return getArrayProxy(target[prop], target, handlers.newArrayHandler)
744
- }
745
- return target[prop]
746
- }
747
- },
748
- set(target, prop, value) {
749
- if (prop === isChanged || prop === parent) {
750
- // prevent infinite loops, parent is only needed to mark it isChanged
751
- // but this is a new array proxy, parent is already dirty
752
- return true
753
- }
754
- if (meta.access && !meta.access(target, prop)) {
755
- return undefined
756
- }
757
- if (JSONTag.getType(value)==='object' && !value[isProxy]) {
758
- value = getNewValueProxy(value)
759
- }
760
- target[prop] = value
761
- return true
470
+ this.next('"')
471
+ while(this.ch) {
472
+ if (this.ch==='"') {
473
+ this.next()
474
+ let bytes = new Uint8Array(value)
475
+ value = decoder.decode(bytes)
476
+ this.checkStringType(tagName, value)
477
+ return value
762
478
  }
763
- },
764
- newValueHandler: {
765
- get(target, prop, receiver) {
766
- switch(prop) {
767
- case resultSet:
768
- return meta.resultArray
769
- break;
770
- case source:
771
- return target
772
- break
773
- case isProxy:
774
- return true
775
- break
776
- case proxyType:
777
- return 'new'
778
- break
779
- case getBuffer:
780
- return (i) => {
781
- let index = target[getIndex]
782
- if (i != index) {
783
- return encoder.encode('~'+index)
784
- }
785
- return serialize(target, {meta, skipLength:true})
786
- }
787
- break
788
- case getIndex:
789
- return target[getIndex]
790
- break
791
- case isChanged:
792
- return true
793
- break
794
- default:
795
- if (meta.access && !meta.access(target, prop, 'get')) {
796
- return undefined
797
- }
798
- if (Array.isArray(target[prop])) {
799
- return getArrayProxy(target[prop], target, handlers.newArrayHandler)
479
+ if (this.ch==='\\') {
480
+ this.next()
481
+ if (this.ch==='u') {
482
+ for (i=0; i<4; i++) {
483
+ hex = parseInt(this.next(), 16)
484
+ if (!this.isFinite(hex)) {
485
+ break
800
486
  }
801
- return target[prop]
802
- break
803
- }
804
- },
805
- set(target, prop, value) {
806
- if (meta.access && !meta.access(target, prop, 'set')) {
807
- return undefined
808
- }
809
- if (JSONTag.getType(value)==='object' && !value[isProxy]) {
810
- value = getNewValueProxy(value)
811
- }
812
- target[prop] = value
813
- return true
814
- }
815
- },
816
- arrayHandler: {
817
- get(target, prop, receiver) {
818
- const value = target?.[prop]
819
- if (value instanceof Function) {
820
- // if (['copyWithin','fill','pop','push','reverse','shift','sort','splice','unshift'].indexOf(prop)!==-1) {
821
- // if (immutable) {
822
- // throw new Error('dataspace is immutable')
823
- // }
824
- // }
825
- return (...args) => {
826
- args = args.map(arg => {
827
- if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
828
- arg = getNewValueProxy(arg)
829
- }
830
- return arg
831
- })
832
- return value.apply(receiver, args)
487
+ uffff = uffff * 16 + hex
833
488
  }
834
- } else if (prop===isChanged) {
835
- return target[isChanged] || target[parent][isChanged]
836
- } else if (prop===source) {
837
- return target
489
+ let str = String.fromCharCode(uffff)
490
+ let bytes = encoder.encode(str)
491
+ value.push.apply(value, bytes)
492
+ this.next()
493
+ } else if (typeof this.escapee[this.ch] === 'string') {
494
+ value.push(this.escapee[this.ch].charCodeAt(0))
495
+ this.next()
838
496
  } else {
839
- if (meta.access && !meta.access(target, prop, 'get')) {
840
- return undefined
841
- }
842
- if (Array.isArray(value)) {
843
- return getArrayProxy(value, target)
844
- }
845
- return value
846
- }
847
- },
848
- set(target, prop, value) {
849
- if (prop == parent) {
850
- target[parent] = value
851
- return true
852
- }
853
- if (immutable) {
854
- throw new Error('dataspace is immutable')
855
- }
856
- if (meta.access && !meta.access(target, prop, 'set')) {
857
- return undefined
858
- }
859
- if (JSONTag.getType(value)==='object' && !value[isProxy]) {
860
- value = getNewValueProxy(value)
861
- }
862
- if (target[prop] === value) {
863
- return true
864
- }
865
- target[prop] = value
866
- target[isChanged] = true
867
- target[parent][isChanged] = true
868
- return true
869
- },
870
- deleteProperty(target, prop) {
871
- if (immutable) {
872
- throw new Error('dataspace is immutable')
873
- }
874
- if (meta.access && !meta.access(target, prop, 'deleteProperty')) {
875
- return undefined
876
- }
877
- //FIXME: if target[prop] was the last reference to an object
878
- //that object should be deleted so that its line will become empty
879
- //when stringifying resultArray again
880
- if (typeof target[prop] === 'undefined') {
881
- return true
882
- }
883
- delete target[prop]
884
- target[isChanged] = true
885
- target[parent][isChanged] = true
886
- return true
887
- }
888
- },
889
- handler: {
890
- get(target, prop, receiver) {
891
- switch(prop) {
892
- case resultSet:
893
- return meta.resultArray
894
- break;
895
- case isProxy:
896
- return true
897
- break
898
- case proxyType:
899
- return 'parse'
900
- break
901
- case getBuffer:
902
- return (i) => {
903
- let index = target[getIndex]
904
- if (i != index) {
905
- return encoder.encode('~'+index)
906
- }
907
- if (target[isChanged]) {
908
- return serialize(target, {skipLength: true})
909
- }
910
- return target[position].input.slice(target[position].start,target[position].end)
911
- }
912
- break
913
- case getIndex:
914
- return target[getIndex]
915
- break
916
- case isChanged:
917
- return target[isChanged]
918
497
  break
919
498
  }
920
- firstParse(target, receiver)
921
- switch(prop) {
922
- case source:
923
- if (meta.access && !meta.access(target, prop, 'get')) {
924
- return undefined
925
- }
926
- return target
927
- break
928
- default:
929
- if (meta.access && !meta.access(target, prop, 'get')) {
930
- return undefined
931
- }
932
- if (Array.isArray(target[prop])) {
933
- return getArrayProxy(target[prop], target)
934
- }
935
- return target[prop]
936
- break
937
- }
938
- },
939
- set(target, prop, value, receiver) {
940
- if (immutable && prop!==resultSet && prop!==source && prop!==isChanged) {
941
- throw new Error('dataspace is immutable')
942
- }
943
- switch(prop) {
944
- case isChanged:
945
- break
946
- case source:
947
- resetObject(target)
948
- target[position] = value[position]
949
- target[isParsed] = false
950
- target[isChanged] = false
951
- return true
952
- break
953
- case resultSet:
954
- break
955
- }
956
- firstParse(target, receiver)
957
- if (meta.access && !meta.access(target, prop, 'set')) {
958
- return undefined
959
- }
960
- if (value && JSONTag.getType(value)==='object' && !value[isProxy]) {
961
- value = getNewValueProxy(value)
962
- }
963
- if (target[prop] === value) {
964
- return true
965
- }
966
- target[prop] = value
967
- target[isChanged] = true
968
- return true
969
- },
970
- deleteProperty(target, prop) {
971
- if (immutable) {
972
- throw new Error('dataspace is immutable')
973
- }
974
- if (meta.access && !meta.access(target, prop, 'deleteProperty')) {
975
- return undefined
976
- }
977
- firstParse(target)
978
- if (typeof target[prop] === 'undefined') {
979
- return true
980
- }
981
- delete target[prop]
982
- target[isChanged] = true
983
- return true
984
- },
985
- ownKeys(target) {
986
- firstParse(target)
987
- return Reflect.ownKeys(target)
988
- },
989
- getOwnPropertyDescriptor(target, prop) {
990
- firstParse(target)
991
- return Reflect.getOwnPropertyDescriptor(target, prop)
992
- },
993
- defineProperty(target, prop, descriptor) {
994
- if (immutable) {
995
- throw new Error('dataspace is immutable')
996
- }
997
- if (meta.access && !meta.access(target, prop, 'defineProperty')) {
998
- return undefined
999
- }
1000
- firstParse(target)
1001
- target[isChanged] = true
1002
- return Object.defineProperty(target, prop, descriptor)
1003
- },
1004
- has(target, prop) {
1005
- if (meta.access && !meta.access(target, prop, 'has')) {
1006
- return false
1007
- }
1008
- firstParse()
1009
- return prop in target
1010
- },
1011
- setPrototypeOf(target,proto) {
1012
- throw new Error('changing prototypes is not supported')
499
+ } else {
500
+ value.push(this.ch.charCodeAt(0))
501
+ this.next()
1013
502
  }
1014
503
  }
504
+ this.error("Syntax error: incomplete string")
1015
505
  }
1016
506
 
1017
- const firstParse = function(target, receiver) {
1018
- if (!target[isParsed]) {
1019
- parseValue(target[position], target)
1020
- target[isParsed] = true
1021
- if (receiver) {
1022
- let tag = JSONTag.getType(target)
1023
- if (tag) {
1024
- JSONTag.setType(receiver, tag)
1025
- }
1026
- let attributes = JSONTag.getAttributes(target)
1027
- if (attributes) {
1028
- JSONTag.setAttributes(receiver, attributes)
1029
- }
1030
- }
507
+ length()
508
+ {
509
+ this.whitespace()
510
+ this.next('(')
511
+ let numString=''
512
+ while(this.ch>='0' && this.ch<='9') {
513
+ numString += this.ch
514
+ this.next()
515
+ }
516
+ if (this.ch!==')') {
517
+ this.error('Syntax error: not a length')
1031
518
  }
519
+ this.next()
520
+ return parseInt(numString)
1032
521
  }
1033
522
 
1034
- function resetObject(ob) {
1035
- for (let prop of Object.getOwnPropertyNames(ob)) {
1036
- delete ob[prop]
523
+ offset()
524
+ {
525
+ this.next('~')
526
+ let numString = ''
527
+ while(this.ch>='0' && this.ch<='9') {
528
+ numString += this.ch
529
+ this.next()
1037
530
  }
531
+ if (this.ch=='-') {
532
+ this.next('-')
533
+ let endString = ''
534
+ while(this.ch>='0' && this.ch<='9') {
535
+ endString += this.ch
536
+ this.next()
537
+ }
538
+ return new Slice(parseInt(numString),parseInt(endString)+1) // +1 because array.slice(start,end) slices upto but not including end
539
+ }
540
+ return parseInt(numString)
1038
541
  }
1039
542
 
1040
- const getNewValueProxy = function(value) {
1041
- if (value === null) {
1042
- return null
543
+ parseValue(position, ob={})
544
+ {
545
+ this.input = position.input
546
+ this.at = position.start
547
+ this.next()
548
+ let result = this.value(ob)
549
+ if (result instanceof JSONTag.Link) {
550
+ result = this.handleLink(result)
1043
551
  }
1044
- let index = meta.resultArray.length
1045
- meta.resultArray.push('')
1046
- value[getIndex] = index
1047
- makeChildProxies(value)
1048
- let result = new Proxy(value, handlers.newValueHandler)
1049
- meta.resultArray[index] = result
1050
552
  return result
1051
553
  }
1052
554
 
1053
- let valueProxy = function(length, index)
555
+ handleLink(link)
1054
556
  {
1055
- let cache = {}
1056
- cache[getIndex] = index
1057
- cache[isChanged] = false
1058
- cache[isParsed] = false
1059
- // current offset + length contains jsontag of this value
1060
- cache[position] = {
1061
- input,
1062
- start: at-1,
1063
- end: at-1+length
557
+ let id = ''+link
558
+ let links = this.meta.unresolved.get(id)
559
+ if (links.length) {
560
+ throw Error('nyi')
1064
561
  }
1065
- at += length
1066
- next()
1067
- // newValueHandler makes sure that value[getBuffer] runs stringify
1068
- // arrayHandler makes sure that changes in the array set targetIsChanged to true
1069
- return new Proxy(cache, handlers.handler)
1070
562
  }
1071
563
 
1072
564
  value = function(ob={})
1073
565
  {
1074
566
  let tagOb, result, tagName;
1075
- whitespace()
1076
- if (ch==='~') {
1077
- let vOffset = offset()
567
+ this.whitespace()
568
+ if (this.ch==='~') {
569
+ let vOffset = this.offset()
1078
570
  if (isSlice(vOffset)) {
1079
571
  return vOffset
1080
572
  }
1081
- return meta.resultArray[vOffset]
573
+ return this.meta.resultArray[vOffset]
1082
574
  }
1083
- if (ch==='<') {
1084
- tagOb = tag()
575
+ if (this.ch==='<') {
576
+ tagOb = this.tag()
1085
577
  tagName = tagOb.tagName
1086
- whitespace()
578
+ this.whitespace()
1087
579
  }
1088
- switch(ch) {
580
+ switch(this.ch) {
1089
581
  case '{':
1090
582
  if (tagName && tagName!=='object') {
1091
- isTypeError(tagName, ch)
583
+ this.typeError(tagName, this.ch)
1092
584
  }
1093
- result = object(ob)
585
+ result = this.object(ob)
1094
586
  break
1095
587
  case '[':
1096
588
  if (tagName && tagName!=='array') {
1097
- isTypeError(tagName, ch)
589
+ this.typeError(tagName, this.ch)
1098
590
  }
1099
- result = array()
591
+ result = this.array()
1100
592
  break
1101
593
  case '"':
1102
- result = string(tagName)
594
+ result = this.string(tagName)
1103
595
  break
1104
596
  case '-':
1105
- result = number(tagName)
597
+ result = this.number(tagName)
1106
598
  break
1107
599
  default:
1108
- if (ch>='0' && ch<='9') {
1109
- result = number(tagName)
600
+ if (this.ch>='0' && this.ch<='9') {
601
+ result = this.number(tagName)
1110
602
  } else {
1111
- result = boolOrNull(tagName)
603
+ result = this.boolOrNull(tagName)
1112
604
  }
1113
605
  break
1114
606
  }
@@ -1125,7 +617,7 @@ export default function parse(input, meta, immutable=true)
1125
617
  result = new Number(result)
1126
618
  break
1127
619
  default:
1128
- error('Syntax Error: unexpected type '+(typeof result))
620
+ this.error('Syntax Error: unexpected type '+(typeof result))
1129
621
  break
1130
622
  }
1131
623
  }
@@ -1138,41 +630,139 @@ export default function parse(input, meta, immutable=true)
1138
630
  }
1139
631
  return result
1140
632
  }
1141
-
1142
- function jump() {
1143
- next('+')
1144
- return number()
633
+
634
+ jump()
635
+ {
636
+ this.next('+')
637
+ return this.number()
1145
638
  }
1146
639
 
1147
- function lengthValue(i) {
1148
- whitespace()
1149
- if (!ch) {
1150
- next()
640
+ lengthValue(i)
641
+ {
642
+ this.whitespace()
643
+ if (!this.ch) {
644
+ this.next()
1151
645
  }
1152
646
  let l, v
1153
- if (ch=='+') {
1154
- i += jump()
647
+ if (this.ch=='+') {
648
+ i += this.jump()
1155
649
  } else {
1156
- l = length()
1157
- v = valueProxy(l,i)
650
+ l = this.length()
651
+ v = this.valueProxy(l,i)
1158
652
  }
1159
653
  return [l, v, i]
1160
654
  }
1161
655
 
1162
- let line = 0
1163
- while(ch && at<input.length) {
1164
- result = lengthValue(line) // needs to return current line nr
1165
- whitespace()
1166
- offsetArray.push(at)
1167
- line = result[2]
1168
- if (result[1]) {
1169
- if (!meta.resultArray[line] || meta.resultArray[line][proxyType]=='new') {
1170
- meta.resultArray[line] = result[1]
1171
- } else {
1172
- meta.resultArray[line][source] = result[1]
656
+ valueProxy(length, index)
657
+ {
658
+ let cache = {}
659
+ cache[getIndex] = index
660
+ cache[isChanged] = false
661
+ cache[isParsed] = false
662
+ // current offset + length contains jsontag of this value
663
+ cache[position] = {
664
+ input: this.input,
665
+ start: this.at-1,
666
+ end: this.at-1+length
667
+ }
668
+ this.at += length
669
+ this.next()
670
+ // newValueHandler makes sure that value[getBuffer] runs stringify
671
+ // arrayHandler makes sure that changes in the array set targetIsChanged to true
672
+ let result = new Proxy(cache, this.handlers.defaultHandler)
673
+ this.cachedProxies.set(cache, result)
674
+ return result
675
+ }
676
+
677
+ makeChildProxies(parent)
678
+ {
679
+ Object.entries(parent).forEach(([key,entry]) => {
680
+ if (Array.isArray(entry)) {
681
+ this.makeChildProxies(entry)
682
+ } else if (entry && JSONTag.getType(entry)==='object') {
683
+ if (entry[isProxy]) {
684
+ // do nothing
685
+ } else {
686
+ parent[key] = this.getNewValueProxy(entry)
687
+ }
688
+ }
689
+ })
690
+ }
691
+
692
+ getArrayProxy(arr, par, handler)
693
+ {
694
+ if (!handler) {
695
+ handler = this.handlers.arrayHandler
696
+ }
697
+ if (!this.cachedProxies.has(arr)) {
698
+ this.cachedProxies.set(arr, new Proxy(arr, handler))
699
+ }
700
+ let aProxy = this.cachedProxies.get(arr)
701
+ aProxy[parent] = par
702
+ return aProxy
703
+ }
704
+
705
+ firstParse(target)
706
+ {
707
+ if (!target[isParsed]) {
708
+ this.parseValue(target[position], target)
709
+ target[isParsed] = true
710
+ }
711
+ }
712
+
713
+
714
+ getNewValueProxy(value)
715
+ {
716
+ if (value === null) {
717
+ return null
718
+ }
719
+ let index = this.meta.resultArray.length
720
+ this.meta.resultArray.push('')
721
+ value[getIndex] = index
722
+ this.makeChildProxies(value)
723
+ let result = new Proxy(value, this.handlers.newValueHandler)
724
+ this.cachedProxies.set(value, result)
725
+ this.meta.resultArray[index] = result
726
+ return result
727
+ }
728
+
729
+ parse(input)
730
+ {
731
+ if (typeof input == 'string' || input instanceof String) {
732
+ input = stringToSAB(input)
733
+ }
734
+ if (!(input instanceof Uint8Array)) {
735
+ this.error('parse only accepts Uint8Array or String as input')
736
+ }
737
+ if (!this.meta.resultArray) {
738
+ this.meta.resultArray = []
739
+ }
740
+
741
+ this.ch = ' '
742
+ this.at = 0
743
+ this.input = input
744
+
745
+ let line = 0
746
+ while(this.ch && this.at<this.input.length) {
747
+ let result = this.lengthValue(line) // needs to return current line nr
748
+ this.whitespace()
749
+ line = result[2]
750
+ if (result[1]) {
751
+ if (!this.meta.resultArray[line] || this.meta.resultArray[line][proxyType]=='new') {
752
+ this.meta.resultArray[line] = result[1]
753
+ } else {
754
+ this.meta.resultArray[line][source] = result[1]
755
+ }
756
+ line++
1173
757
  }
1174
- line++
1175
758
  }
759
+ return this.meta.resultArray[0]
760
+ }
761
+
762
+ checkUnresolved() {
763
+ // TODO:
764
+ // for now assume there are no <link> objects in od-jsontag
765
+ // JSONTag Parser.checkUnresolved triggers firstParse,
766
+ // while parsing the current object
1176
767
  }
1177
- return meta.resultArray[0]
1178
768
  }