@muze-nl/od-jsontag 0.3.4 → 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
@@ -1,13 +1,13 @@
1
1
  import JSONTag from '@muze-nl/jsontag';
2
2
  import Null from '@muze-nl/jsontag/src/lib/Null.mjs'
3
3
  import serialize from './serialize.mjs'
4
- import {source,isProxy,proxyType,getBuffer,getIndex,isChanged,isParsed,isReceived,position,parent,resultSet} from './symbols.mjs'
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
373
  }
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
- }
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,479 +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()
458
+ this.next(',')
459
+ this.whitespace()
650
460
  }
651
- error("Input stopped early")
461
+ this.error("Input stopped early")
652
462
  }
653
463
 
654
- let length = function()
464
+ string(tagName)
655
465
  {
656
- whitespace()
657
- next('(')
658
- let numString=''
659
- while(ch>='0' && ch<='9') {
660
- numString += ch
661
- next()
662
- }
663
- if (ch!==')') {
664
- error('Syntax error: not a length')
665
- }
666
- next()
667
- return parseInt(numString)
668
- }
669
-
670
- let offset = function()
671
- {
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
- break
919
- }
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
497
  break
937
498
  }
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
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()
1021
515
  }
1022
- if (receiver && !target[isReceived]) {
1023
- //FIXME: this breaks on a Proxy without receiver param
1024
- //e.g. odJSONTag and a call to ownKeys triggering the first parse
1025
- let tag = JSONTag.getType(target)
1026
- if (tag) {
1027
- JSONTag.setType(receiver, tag)
1028
- }
1029
- let attributes = JSONTag.getAttributes(target)
1030
- if (attributes) {
1031
- JSONTag.setAttributes(receiver, attributes)
1032
- }
1033
- target[isReceived] = true
516
+ if (this.ch!==')') {
517
+ this.error('Syntax error: not a length')
1034
518
  }
519
+ this.next()
520
+ return parseInt(numString)
1035
521
  }
1036
522
 
1037
- function resetObject(ob) {
1038
- for (let prop of Object.getOwnPropertyNames(ob)) {
1039
- 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()
1040
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)
1041
541
  }
1042
542
 
1043
- const getNewValueProxy = function(value) {
1044
- if (value === null) {
1045
- 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)
1046
551
  }
1047
- let index = meta.resultArray.length
1048
- meta.resultArray.push('')
1049
- value[getIndex] = index
1050
- makeChildProxies(value)
1051
- let result = new Proxy(value, handlers.newValueHandler)
1052
- meta.resultArray[index] = result
1053
552
  return result
1054
553
  }
1055
554
 
1056
- let valueProxy = function(length, index)
555
+ handleLink(link)
1057
556
  {
1058
- let cache = {}
1059
- cache[getIndex] = index
1060
- cache[isChanged] = false
1061
- cache[isParsed] = false
1062
- // current offset + length contains jsontag of this value
1063
- cache[position] = {
1064
- input,
1065
- start: at-1,
1066
- end: at-1+length
557
+ let id = ''+link
558
+ let links = this.meta.unresolved.get(id)
559
+ if (links.length) {
560
+ throw Error('nyi')
1067
561
  }
1068
- at += length
1069
- next()
1070
- // newValueHandler makes sure that value[getBuffer] runs stringify
1071
- // arrayHandler makes sure that changes in the array set targetIsChanged to true
1072
- return new Proxy(cache, handlers.handler)
1073
562
  }
1074
563
 
1075
564
  value = function(ob={})
1076
565
  {
1077
566
  let tagOb, result, tagName;
1078
- whitespace()
1079
- if (ch==='~') {
1080
- let vOffset = offset()
567
+ this.whitespace()
568
+ if (this.ch==='~') {
569
+ let vOffset = this.offset()
1081
570
  if (isSlice(vOffset)) {
1082
571
  return vOffset
1083
572
  }
1084
- return meta.resultArray[vOffset]
573
+ return this.meta.resultArray[vOffset]
1085
574
  }
1086
- if (ch==='<') {
1087
- tagOb = tag()
575
+ if (this.ch==='<') {
576
+ tagOb = this.tag()
1088
577
  tagName = tagOb.tagName
1089
- whitespace()
578
+ this.whitespace()
1090
579
  }
1091
- switch(ch) {
580
+ switch(this.ch) {
1092
581
  case '{':
1093
582
  if (tagName && tagName!=='object') {
1094
- isTypeError(tagName, ch)
583
+ this.typeError(tagName, this.ch)
1095
584
  }
1096
- result = object(ob)
585
+ result = this.object(ob)
1097
586
  break
1098
587
  case '[':
1099
588
  if (tagName && tagName!=='array') {
1100
- isTypeError(tagName, ch)
589
+ this.typeError(tagName, this.ch)
1101
590
  }
1102
- result = array()
591
+ result = this.array()
1103
592
  break
1104
593
  case '"':
1105
- result = string(tagName)
594
+ result = this.string(tagName)
1106
595
  break
1107
596
  case '-':
1108
- result = number(tagName)
597
+ result = this.number(tagName)
1109
598
  break
1110
599
  default:
1111
- if (ch>='0' && ch<='9') {
1112
- result = number(tagName)
600
+ if (this.ch>='0' && this.ch<='9') {
601
+ result = this.number(tagName)
1113
602
  } else {
1114
- result = boolOrNull(tagName)
603
+ result = this.boolOrNull(tagName)
1115
604
  }
1116
605
  break
1117
606
  }
@@ -1128,7 +617,7 @@ export default function parse(input, meta, immutable=true)
1128
617
  result = new Number(result)
1129
618
  break
1130
619
  default:
1131
- error('Syntax Error: unexpected type '+(typeof result))
620
+ this.error('Syntax Error: unexpected type '+(typeof result))
1132
621
  break
1133
622
  }
1134
623
  }
@@ -1141,41 +630,139 @@ export default function parse(input, meta, immutable=true)
1141
630
  }
1142
631
  return result
1143
632
  }
1144
-
1145
- function jump() {
1146
- next('+')
1147
- return number()
633
+
634
+ jump()
635
+ {
636
+ this.next('+')
637
+ return this.number()
1148
638
  }
1149
639
 
1150
- function lengthValue(i) {
1151
- whitespace()
1152
- if (!ch) {
1153
- next()
640
+ lengthValue(i)
641
+ {
642
+ this.whitespace()
643
+ if (!this.ch) {
644
+ this.next()
1154
645
  }
1155
646
  let l, v
1156
- if (ch=='+') {
1157
- i += jump()
647
+ if (this.ch=='+') {
648
+ i += this.jump()
1158
649
  } else {
1159
- l = length()
1160
- v = valueProxy(l,i)
650
+ l = this.length()
651
+ v = this.valueProxy(l,i)
1161
652
  }
1162
653
  return [l, v, i]
1163
654
  }
1164
655
 
1165
- let line = 0
1166
- while(ch && at<input.length) {
1167
- result = lengthValue(line) // needs to return current line nr
1168
- whitespace()
1169
- offsetArray.push(at)
1170
- line = result[2]
1171
- if (result[1]) {
1172
- if (!meta.resultArray[line] || meta.resultArray[line][proxyType]=='new') {
1173
- meta.resultArray[line] = result[1]
1174
- } else {
1175
- 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++
1176
757
  }
1177
- line++
1178
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
1179
767
  }
1180
- return meta.resultArray[0]
1181
768
  }