@muze-nl/od-jsontag 0.3.4 → 0.4.1

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,459 @@ 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
+ switch(prop) {
64
+ case source:
65
+ return target
66
+ break
67
+ case isProxy:
68
+ return true
69
+ break
70
+ case proxyType:
71
+ return 'new'
72
+ break
73
+ }
74
+ if (target[prop] instanceof Function) {
75
+ return (...args) => {
76
+ args = args.map(arg => {
77
+ const type = JSONTag.getType(arg)
78
+ if (type==='object' || type==='link') {
79
+ arg = this.getNewValueProxy(arg)
80
+ }
81
+ return arg
82
+ })
83
+ return target[prop].apply(target, args)
84
+ }
85
+ } else if (prop===isChanged) {
86
+ return true
87
+ } else {
88
+ if (this.meta.access && !this.meta.access(target, prop)) {
89
+ return undefined
90
+ }
91
+ if (Array.isArray(target[prop])) {
92
+ return this.getArrayProxy(target[prop], target, this.handlers.newArrayHandler)
93
+ }
94
+ return target[prop]
95
+ }
96
+ },
97
+ set: (target, prop, value) => {
98
+ if (prop === isChanged || prop === parent) {
99
+ // prevent infinite loops, parent is only needed to mark it isChanged
100
+ // but this is a new array proxy, parent is already dirty
101
+ return true
102
+ }
103
+ if (this.meta.access && !this.meta.access(target, prop)) {
104
+ return undefined
105
+ }
106
+ const type = JSONTag.getType(arg)
107
+ if ((type==='object' || type==='link')
108
+ && typeof prop !== 'symbol'
109
+ ) {
110
+ value = this.getNewValueProxy(value)
111
+ }
112
+ target[prop] = value
113
+ return true
217
114
  }
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')
115
+ },
116
+ newValueHandler: {
117
+ get: (target, prop) => {
118
+ switch(prop) {
119
+ case resultSet:
120
+ return this.meta.resultArray
121
+ break;
122
+ case source:
123
+ return target
124
+ break
125
+ case isProxy:
126
+ return true
127
+ break
128
+ case proxyType:
129
+ return 'new'
130
+ break
131
+ case getBuffer:
132
+ return (i) => {
133
+ let index = target[getIndex]
134
+ if (i != index) {
135
+ return encoder.encode('~'+index)
136
+ }
137
+ return serialize(target, {meta:this.meta, skipLength:true})
138
+ }
139
+ break
140
+ case getIndex:
141
+ return target[getIndex]
142
+ break
143
+ case isChanged:
144
+ return true
145
+ break
146
+ default:
147
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
148
+ return undefined
149
+ }
150
+ if (Array.isArray(target[prop])) {
151
+ return this.getArrayProxy(target[prop], target, this.handlers.newArrayHandler)
152
+ }
153
+ return target[prop]
154
+ break
155
+ }
156
+ },
157
+ set: (target, prop, value) => {
158
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
159
+ return undefined
160
+ }
161
+ const type = JSONTag.getType(arg)
162
+ if ((type==='object' || type==='link')
163
+ && typeof prop !== 'symbol'
164
+ ) {
165
+ value = this.getNewValueProxy(value)
166
+ }
167
+ target[prop] = value
168
+ return true
233
169
  }
234
- }
235
- if (typeof range[1] === 'number') {
236
- if (test>range[1]) {
237
- error('Syntax Error: integer value out of range')
170
+ },
171
+ arrayHandler: {
172
+ get: (target, prop, receiver) => {
173
+ switch(prop) {
174
+ case source:
175
+ return target
176
+ break
177
+ case isProxy:
178
+ return true
179
+ break
180
+ case proxyType:
181
+ return 'array'
182
+ break
183
+ }
184
+ const value = target?.[prop]
185
+ if (value instanceof Function) {
186
+ if (['copyWithin','fill','pop','push','reverse','shift','sort','splice','unshift'].indexOf(prop)!==-1) {
187
+ if (this.immutable) {
188
+ throw new Error('dataspace is immutable')
189
+ }
190
+ }
191
+ return (...args) => {
192
+ args = args.map(arg => {
193
+ if (!arg[isProxy]) {
194
+ let type = JSONTag.getType(arg)
195
+ if (type==='object' || type==='link') { //FIXME: check if other types need handling
196
+ arg = this.getNewValueProxy(arg)
197
+ }
198
+ }
199
+ return arg
200
+ })
201
+ return value.apply(receiver, args)
202
+ }
203
+ } else if (prop===isChanged) {
204
+ return target[isChanged] || target[parent][isChanged]
205
+ } else if (prop===source) {
206
+ return target
207
+ } else {
208
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
209
+ return undefined
210
+ }
211
+ if (Array.isArray(value)) {
212
+ return this.getArrayProxy(value, target)
213
+ }
214
+ return value
215
+ }
216
+ },
217
+ set: (target, prop, value) => {
218
+ if (prop == parent) {
219
+ target[parent] = value
220
+ return true
221
+ }
222
+ if (this.immutable) {
223
+ throw new Error('dataspace is immutable')
224
+ }
225
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
226
+ return undefined
227
+ }
228
+ const type = JSONTag.getType(value)
229
+ if ((type==='object' || type==='link') //FIXME: check if other types need handling
230
+ && typeof prop !== 'symbol'
231
+ ) {
232
+ value = this.getNewValueProxy(value)
233
+ }
234
+ if (target[prop] === value) {
235
+ return true
236
+ }
237
+ target[prop] = value
238
+ target[isChanged] = true
239
+ target[parent][isChanged] = true
240
+ return true
241
+ },
242
+ deleteProperty: (target, prop) => {
243
+ if (this.immutable) {
244
+ throw new Error('dataspace is immutable')
245
+ }
246
+ if (this.meta.access && !this.meta.access(target, prop, 'deleteProperty')) {
247
+ return undefined
248
+ }
249
+ //FIXME: if target[prop] was the last reference to an object
250
+ //that object should be deleted so that its line will become empty
251
+ //when stringifying resultArray again
252
+ if (typeof target[prop] === 'undefined') {
253
+ return true
254
+ }
255
+ delete target[prop]
256
+ target[isChanged] = true
257
+ target[parent][isChanged] = true
258
+ return true
238
259
  }
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)) {
260
+ },
261
+ defaultHandler: {
262
+ get: (target, prop, receiver) => {
263
+ switch(prop) {
264
+ case resultSet:
265
+ return this.meta.resultArray
266
+ break;
267
+ case isProxy:
268
+ return true
269
+ break
270
+ case proxyType:
271
+ return 'parse'
272
+ break
273
+ case getBuffer:
274
+ return (i) => {
275
+ let index = target[getIndex]
276
+ if (i != index) {
277
+ return encoder.encode('~'+index)
278
+ }
279
+ if (target[isChanged]) {
280
+ return serialize(target, {skipLength: true})
281
+ }
282
+ return target[position].input.slice(target[position].start,target[position].end)
283
+ }
284
+ break
285
+ case getIndex:
286
+ return target[getIndex]
287
+ break
288
+ case isChanged:
289
+ return target[isChanged]
290
+ break
291
+ }
292
+ this.firstParse(target, receiver)
293
+ switch(prop) {
294
+ case source:
295
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
296
+ return undefined
297
+ }
298
+ return target
299
+ break
300
+ default:
301
+ if (this.meta.access && !this.meta.access(target, prop, 'get')) {
302
+ return undefined
303
+ }
304
+ if (Array.isArray(target[prop])) {
305
+ return this.getArrayProxy(target[prop], target)
306
+ }
307
+ return target[prop]
308
+ break
309
+ }
310
+ },
311
+ set: (target, prop, value, receiver) => {
312
+ if (this.immutable && prop!==resultSet && prop!==source && prop!==isChanged) {
313
+ throw new Error('dataspace is immutable')
314
+ }
315
+ switch(prop) {
316
+ case isChanged:
317
+ break
318
+ case source:
319
+ resetObject(target)
320
+ target[position] = value[position]
321
+ target[isParsed] = false
322
+ target[isChanged] = false
323
+ return true
324
+ break
325
+ case resultSet:
440
326
  break
441
- }
442
- uffff = uffff * 16 + hex
443
327
  }
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
328
+ this.firstParse(target, receiver)
329
+ if (this.meta.access && !this.meta.access(target, prop, 'set')) {
330
+ return undefined
331
+ }
332
+ const type = JSONTag.getType(value)
333
+ if ((type==='object' || type==='link') //FIXME: check if other types need handling
334
+ && typeof prop !== 'symbol'
335
+ ) {
336
+ value = this.getNewValueProxy(value)
337
+ }
338
+ if (target[prop] === value) {
339
+ return true
340
+ }
341
+ target[prop] = value
342
+ target[isChanged] = true
343
+ return true
344
+ },
345
+ deleteProperty: (target, prop) => {
346
+ if (this.immutable) {
347
+ throw new Error('dataspace is immutable')
348
+ }
349
+ if (this.meta.access && !this.meta.access(target, prop, 'deleteProperty')) {
350
+ return undefined
351
+ }
352
+ this.firstParse(target)
353
+ if (typeof target[prop] === 'undefined') {
354
+ return true
355
+ }
356
+ delete target[prop]
357
+ target[isChanged] = true
358
+ return true
359
+ },
360
+ ownKeys: (target) => {
361
+ this.firstParse(target)
362
+ return Reflect.ownKeys(target)
363
+ },
364
+ getOwnPropertyDescriptor: (target, prop) => {
365
+ this.firstParse(target)
366
+ return Reflect.getOwnPropertyDescriptor(target, prop)
367
+ },
368
+ defineProperty: (target, prop, descriptor) => {
369
+ if (this.immutable) {
370
+ throw new Error('dataspace is immutable')
371
+ }
372
+ if (this.meta.access && !this.meta.access(target, prop, 'defineProperty')) {
373
+ return undefined
374
+ }
375
+ this.firstParse(target)
376
+ target[isChanged] = true
377
+ return Object.defineProperty(target, prop, descriptor)
378
+ },
379
+ has: (target, prop) => {
380
+ if (this.meta.access && !this.meta.access(target, prop, 'has')) {
381
+ return false
382
+ }
383
+ this.firstParse()
384
+ return prop in target
385
+ },
386
+ setPrototypeOf: () => {
387
+ throw new Error('changing prototypes is not supported')
453
388
  }
454
- } else {
455
- value.push(ch.charCodeAt(0))
456
- next()
457
389
  }
458
390
  }
459
- error("Syntax error: incomplete string")
460
391
  }
461
392
 
462
- let tag = function()
393
+ next(c)
463
394
  {
464
- let key, val, tagOb={
465
- attributes: {}
395
+ if (c && c!==this.ch) {
396
+ let source = SABtoString(this.input)
397
+ this.error("Expected '"+c+"' instead of '"+this.ch+"':"+this.at+':'+source)
466
398
  }
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')
399
+ this.ch = String.fromCharCode(this.input.at(this.at))
400
+ this.at+=1
401
+ return this.ch
494
402
  }
495
403
 
496
- let whitespace = function()
404
+ error(m)
497
405
  {
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
- }
510
- }
511
- }
512
-
513
- let word = function()
514
- {
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
- }
406
+ let context
407
+ try {
408
+ context = decoder.decode(this.input.slice(this.at,this.at+100));
409
+ } catch(e) {
529
410
 
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
411
  }
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
- })
412
+ throw {
413
+ name: 'SyntaxError',
414
+ message: m,
415
+ at: this.at,
416
+ input: context
571
417
  }
572
418
  }
573
419
 
574
- let array = function()
420
+ array()
575
421
  {
576
422
  let item, array = []
577
- if (ch !== '[') {
578
- error("Syntax error")
423
+ if (this.ch !== '[') {
424
+ this.error("Syntax error")
579
425
  }
580
- next('[')
581
- whitespace()
582
- if (ch===']') {
583
- next(']')
426
+ this.next('[')
427
+ this.whitespace()
428
+ if (this.ch===']') {
429
+ this.next(']')
584
430
  return array
585
431
  }
586
- while(ch) {
587
- item = value()
588
- checkUnresolved(item, array, array.length)
432
+ while(this.ch) {
433
+ item = this.value()
434
+ this.checkUnresolved(item, array, array.length)
589
435
  if (isSlice(item)) {
590
- array = array.concat(meta.resultArray.slice(item.start, item.end))
436
+ array = array.concat(this.meta.resultArray.slice(item.start, item.end))
591
437
  } else {
592
438
  array.push(item)
593
439
  }
594
- whitespace()
595
- if (ch===']') {
596
- next(']')
440
+ this.whitespace()
441
+ if (this.ch===']') {
442
+ this.next(']')
597
443
  return array
598
444
  }
599
- next(',')
600
- whitespace()
445
+ this.next(',')
446
+ this.whitespace()
601
447
  }
602
- error("Input stopped early")
448
+ this.error("Input stopped early")
603
449
  }
604
450
 
605
- let object = function(object={})
451
+
452
+ object(object={})
606
453
  {
607
454
  let key, val
608
- if (ch !== '{') {
609
- error("Syntax Error")
610
- }
611
- next('{')
612
- whitespace()
613
- if (ch==='}') {
614
- next('}')
455
+ if (this.ch !== '{') {
456
+ this.error("Syntax Error")
457
+ }
458
+ this.next('{')
459
+ this.whitespace()
460
+ resetObject(object)
461
+ if (this.ch==='}') {
462
+ this.next('}')
615
463
  return object
616
464
  }
617
465
  let enumerable = true
618
- while(ch) {
619
- if (ch==='#') {
466
+ while(this.ch) {
467
+ if (this.ch==='#') {
620
468
  enumerable = false
621
- next()
469
+ this.next()
622
470
  } else {
623
471
  enumerable = true
624
472
  }
625
- key = string()
473
+ key = this.string()
626
474
  if (key==='__proto__') {
627
- error("Attempt at prototype pollution")
475
+ this.error("Attempt at prototype pollution")
628
476
  }
629
- whitespace()
630
- next(':')
631
- val = value()
477
+ this.whitespace()
478
+ this.next(':')
479
+ val = this.value()
632
480
  if (!enumerable) {
633
481
  Object.defineProperty(object, key, {
634
482
  configurable: true, //important, must be true, otherwise Proxies cannot use it
@@ -639,479 +487,158 @@ export default function parse(input, meta, immutable=true)
639
487
  } else {
640
488
  object[key] = val
641
489
  }
642
- checkUnresolved(val, object, key)
643
- whitespace()
644
- if (ch==='}') {
645
- next('}')
490
+ this.checkUnresolved(val, object, key)
491
+ this.whitespace()
492
+ if (this.ch==='}') {
493
+ this.next('}')
646
494
  return object
647
495
  }
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()
496
+ this.next(',')
497
+ this.whitespace()
662
498
  }
663
- if (ch!==')') {
664
- error('Syntax error: not a length')
665
- }
666
- next()
667
- return parseInt(numString)
499
+ this.error("Input stopped early")
668
500
  }
669
501
 
670
- let offset = function()
502
+ string(tagName)
671
503
  {
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))
504
+ let value = [], hex, i, uffff;
505
+ if (this.ch !== '"') {
506
+ this.error("Syntax Error")
717
507
  }
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
508
+ this.next('"')
509
+ while(this.ch) {
510
+ if (this.ch==='"') {
511
+ this.next()
512
+ let bytes = new Uint8Array(value)
513
+ value = decoder.decode(bytes)
514
+ this.checkStringType(tagName, value)
515
+ return value
762
516
  }
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)
517
+ if (this.ch==='\\') {
518
+ this.next()
519
+ if (this.ch==='u') {
520
+ for (i=0; i<4; i++) {
521
+ hex = parseInt(this.next(), 16)
522
+ if (!this.isFinite(hex)) {
523
+ break
800
524
  }
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)
525
+ uffff = uffff * 16 + hex
833
526
  }
834
- } else if (prop===isChanged) {
835
- return target[isChanged] || target[parent][isChanged]
836
- } else if (prop===source) {
837
- return target
527
+ let str = String.fromCharCode(uffff)
528
+ let bytes = encoder.encode(str)
529
+ value.push.apply(value, bytes)
530
+ this.next()
531
+ } else if (typeof this.escapee[this.ch] === 'string') {
532
+ value.push(this.escapee[this.ch].charCodeAt(0))
533
+ this.next()
838
534
  } 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
535
  break
919
536
  }
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')
537
+ } else {
538
+ value.push(this.ch.charCodeAt(0))
539
+ this.next()
1013
540
  }
1014
541
  }
542
+ this.error("Syntax error: incomplete string")
1015
543
  }
1016
544
 
1017
- const firstParse = function(target, receiver) {
1018
- if (!target[isParsed]) {
1019
- parseValue(target[position], target)
1020
- target[isParsed] = true
545
+ length()
546
+ {
547
+ this.whitespace()
548
+ this.next('(')
549
+ let numString=''
550
+ while(this.ch>='0' && this.ch<='9') {
551
+ numString += this.ch
552
+ this.next()
1021
553
  }
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
554
+ if (this.ch!==')') {
555
+ this.error('Syntax error: not a length')
1034
556
  }
557
+ this.next()
558
+ return parseInt(numString)
1035
559
  }
1036
560
 
1037
- function resetObject(ob) {
1038
- for (let prop of Object.getOwnPropertyNames(ob)) {
1039
- delete ob[prop]
561
+ offset()
562
+ {
563
+ this.next('~')
564
+ let numString = ''
565
+ while(this.ch>='0' && this.ch<='9') {
566
+ numString += this.ch
567
+ this.next()
1040
568
  }
569
+ if (this.ch=='-') {
570
+ this.next('-')
571
+ let endString = ''
572
+ while(this.ch>='0' && this.ch<='9') {
573
+ endString += this.ch
574
+ this.next()
575
+ }
576
+ return new Slice(parseInt(numString),parseInt(endString)+1) // +1 because array.slice(start,end) slices upto but not including end
577
+ }
578
+ return parseInt(numString)
1041
579
  }
1042
580
 
1043
- const getNewValueProxy = function(value) {
1044
- if (value === null) {
1045
- return null
581
+ parseValue(position, ob={})
582
+ {
583
+ this.input = position.input
584
+ this.at = position.start
585
+ this.next()
586
+ let result = this.value(ob)
587
+ if (result instanceof JSONTag.Link) {
588
+ result = this.handleLink(result)
1046
589
  }
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
590
  return result
1054
591
  }
1055
592
 
1056
- let valueProxy = function(length, index)
593
+ handleLink(link)
1057
594
  {
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
595
+ let id = ''+link
596
+ let links = this.meta.unresolved.get(id)
597
+ if (links.length) {
598
+ throw Error('nyi')
1067
599
  }
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
600
  }
1074
601
 
1075
602
  value = function(ob={})
1076
603
  {
1077
604
  let tagOb, result, tagName;
1078
- whitespace()
1079
- if (ch==='~') {
1080
- let vOffset = offset()
605
+ this.whitespace()
606
+ if (this.ch==='~') {
607
+ let vOffset = this.offset()
1081
608
  if (isSlice(vOffset)) {
1082
609
  return vOffset
1083
610
  }
1084
- return meta.resultArray[vOffset]
611
+ return this.meta.resultArray[vOffset]
1085
612
  }
1086
- if (ch==='<') {
1087
- tagOb = tag()
613
+ if (this.ch==='<') {
614
+ tagOb = this.tag()
1088
615
  tagName = tagOb.tagName
1089
- whitespace()
616
+ this.whitespace()
1090
617
  }
1091
- switch(ch) {
618
+ switch(this.ch) {
1092
619
  case '{':
1093
620
  if (tagName && tagName!=='object') {
1094
- isTypeError(tagName, ch)
621
+ this.typeError(tagName, this.ch)
1095
622
  }
1096
- result = object(ob)
623
+ result = this.object(ob)
1097
624
  break
1098
625
  case '[':
1099
626
  if (tagName && tagName!=='array') {
1100
- isTypeError(tagName, ch)
627
+ this.typeError(tagName, this.ch)
1101
628
  }
1102
- result = array()
629
+ result = this.array()
1103
630
  break
1104
631
  case '"':
1105
- result = string(tagName)
632
+ result = this.string(tagName)
1106
633
  break
1107
634
  case '-':
1108
- result = number(tagName)
635
+ result = this.number(tagName)
1109
636
  break
1110
637
  default:
1111
- if (ch>='0' && ch<='9') {
1112
- result = number(tagName)
638
+ if (this.ch>='0' && this.ch<='9') {
639
+ result = this.number(tagName)
1113
640
  } else {
1114
- result = boolOrNull(tagName)
641
+ result = this.boolOrNull(tagName)
1115
642
  }
1116
643
  break
1117
644
  }
@@ -1128,7 +655,7 @@ export default function parse(input, meta, immutable=true)
1128
655
  result = new Number(result)
1129
656
  break
1130
657
  default:
1131
- error('Syntax Error: unexpected type '+(typeof result))
658
+ this.error('Syntax Error: unexpected type '+(typeof result))
1132
659
  break
1133
660
  }
1134
661
  }
@@ -1141,41 +668,149 @@ export default function parse(input, meta, immutable=true)
1141
668
  }
1142
669
  return result
1143
670
  }
1144
-
1145
- function jump() {
1146
- next('+')
1147
- return number()
671
+
672
+ jump()
673
+ {
674
+ this.next('+')
675
+ return this.number()
1148
676
  }
1149
677
 
1150
- function lengthValue(i) {
1151
- whitespace()
1152
- if (!ch) {
1153
- next()
678
+ lengthValue(i)
679
+ {
680
+ this.whitespace()
681
+ if (!this.ch) {
682
+ this.next()
1154
683
  }
1155
684
  let l, v
1156
- if (ch=='+') {
1157
- i += jump()
685
+ if (this.ch=='+') {
686
+ i += this.jump()
1158
687
  } else {
1159
- l = length()
1160
- v = valueProxy(l,i)
688
+ l = this.length()
689
+ v = this.valueProxy(l,i)
1161
690
  }
1162
691
  return [l, v, i]
1163
692
  }
1164
693
 
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]
694
+ valueProxy(length, index)
695
+ {
696
+ let cache = {}
697
+ cache[getIndex] = index
698
+ cache[isChanged] = false
699
+ cache[isParsed] = false
700
+ // current offset + length contains jsontag of this value
701
+ cache[position] = {
702
+ input: this.input,
703
+ start: this.at-1,
704
+ end: this.at-1+length
705
+ }
706
+ this.at += length
707
+ this.next()
708
+ // newValueHandler makes sure that value[getBuffer] runs stringify
709
+ // arrayHandler makes sure that changes in the array set targetIsChanged to true
710
+ let result = new Proxy(cache, this.handlers.defaultHandler)
711
+ this.cachedProxies.set(cache, result)
712
+ return result
713
+ }
714
+
715
+ makeChildProxies(parent)
716
+ {
717
+ Object.entries(parent).forEach(([key,entry]) => {
718
+ if (Array.isArray(entry)) {
719
+ this.makeChildProxies(entry)
720
+ } else if (entry && !entry[isProxy]) {
721
+ let type = JSONTag.getType(entry)
722
+ if (type==='object' || type==='link') {//FIXME: check for other types
723
+ parent[key] = this.getNewValueProxy(entry)
724
+ }
725
+ }
726
+ })
727
+ }
728
+
729
+ getArrayProxy(arr, par, handler)
730
+ {
731
+ if (!handler) {
732
+ handler = this.handlers.arrayHandler
733
+ }
734
+ if (!this.cachedProxies.has(arr)) {
735
+ this.cachedProxies.set(arr, new Proxy(arr, handler))
736
+ }
737
+ let aProxy = this.cachedProxies.get(arr)
738
+ aProxy[parent] = par
739
+ return aProxy
740
+ }
741
+
742
+ firstParse(target)
743
+ {
744
+ if (!target[isParsed]) {
745
+ this.parseValue(target[position], target)
746
+ target[isParsed] = true
747
+ }
748
+ }
749
+
750
+
751
+ getNewValueProxy(value)
752
+ {
753
+ if (value === null) {
754
+ return null
755
+ }
756
+ if (value[isProxy]) {
757
+ return value
758
+ }
759
+ if (JSONTag.getType(value)=='link') {
760
+ let index = this.meta.index.id.get(''+value)
761
+ if (typeof index != 'undefined') {
762
+ return this.meta.resultArray[index]
1176
763
  }
1177
- line++
1178
764
  }
765
+ let index = this.meta.resultArray.length
766
+ this.meta.resultArray.push('')
767
+ value[getIndex] = index
768
+ this.makeChildProxies(value)
769
+ let result = new Proxy(value, this.handlers.newValueHandler)
770
+ this.cachedProxies.set(value, result)
771
+ this.meta.resultArray[index] = result
772
+ return result
773
+ }
774
+
775
+ parse(input)
776
+ {
777
+ if (typeof input == 'string' || input instanceof String) {
778
+ input = stringToSAB(input)
779
+ }
780
+ if (!(input instanceof Uint8Array)) {
781
+ this.error('parse only accepts Uint8Array or String as input')
782
+ }
783
+ if (!this.meta.resultArray) {
784
+ this.meta.resultArray = []
785
+ }
786
+
787
+ this.ch = ' '
788
+ this.at = 0
789
+ this.input = input
790
+
791
+ let line = 0
792
+ while(this.ch && this.at<this.input.length) {
793
+ let result = this.lengthValue(line) // needs to return current line nr
794
+ this.whitespace()
795
+ line = result[2]
796
+ if (result[1]) {
797
+ if (!this.meta.resultArray[line] || this.meta.resultArray[line][proxyType]=='new') {
798
+ this.meta.resultArray[line] = result[1]
799
+ } else {
800
+ this.meta.resultArray[line][source] = result[1]
801
+ }
802
+ line++
803
+ }
804
+ }
805
+ return this.meta.resultArray[0]
806
+ }
807
+
808
+ checkUnresolved(item, object, key) {
809
+ // TODO:
810
+ // for now assume there are no <link> objects in od-jsontag
811
+ // JSONTag Parser.checkUnresolved triggers firstParse,
812
+ // while parsing the current object
813
+ // incorrect: when adding a new object from a JSONTag string
814
+ // it may contain links which cannot yet be resolved... these need to be handled
1179
815
  }
1180
- return meta.resultArray[0]
1181
816
  }