@muze-nl/od-jsontag 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +39 -0
- package/src/jsontag.mjs +26 -0
- package/src/parse.mjs +998 -0
- package/src/serialize.mjs +184 -0
- package/src/symbols.mjs +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 muze
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# od-jsontag: On Demand JSONTag
|
|
2
|
+
|
|
3
|
+
This library implements a parser and stringifier for a variant of jsontag which is optimized so that you only need to parse objects that you use and skip parsing any other objects.
|
|
4
|
+
This is especially useful to share data between threads or other workers using sharedArrayBuffers, the only shared memory option in javascript currently.
|
|
5
|
+
|
|
6
|
+
The parse function creates in memory Proxy objects that trigger parsing only when accessed. You can use the data as normal objects for most use-cases. The format supports non-enumerable
|
|
7
|
+
properties, which aren't part of the normal JSONTag format. The parse function expects an ArrayBuffer a input.
|
|
8
|
+
|
|
9
|
+
The stringify function creates a sharedArrayBuffer, which represents a file with one object per line. Each line is prefixed with a byte counter that indicates the length of the line. References to other objects are encoded as ~n, where n is the line number (starting at 0).
|
|
10
|
+
|
|
11
|
+
The parse function doesn't build an id index, because that requires parsing all objects. Instead the stringify function builds or updates the id index. It isn't included in the string result.
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@muze-nl/od-jsontag",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "On Demand JSONTag: parse/serialize large datastructures on demand, useful for sharing data between threads",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Auke van Slooten <auke@muze.nl>",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"JSONTag",
|
|
9
|
+
"On Demand Parsing"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/muze-nl/od-jsontag",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/muze-nl/od-jsontag/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"main": "./src/parse.mjs",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "tap test/*.mjs"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/muze-nl/od-jsontag.git"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"eslint": "^8.48.0",
|
|
29
|
+
"tap": "~16.3.7"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@muze-nl/jsontag": "^0.9.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/jsontag.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag'
|
|
2
|
+
import {source} from './symbols.mjs'
|
|
3
|
+
|
|
4
|
+
export function getType(obj) {
|
|
5
|
+
return JSONTag.getType(obj?.[source] ?? obj)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getAttribute(obj, attr) {
|
|
9
|
+
return JSONTag.getAttribute(obj?.[source] ?? obj, attr)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getAttributes(obj) {
|
|
13
|
+
return JSONTag.getAttributes(obj?.[source] ?? obj)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getAttributeString(obj) {
|
|
17
|
+
return JSONTag.getAttributesString(obj?.[source] ?? obj)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTypeString(obj) {
|
|
21
|
+
return JSONTag.getTypeString(obj?.[source] ?? obj)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isNull(obj) {
|
|
25
|
+
return JSONTag.isNull(obj?.[source] ?? obj)
|
|
26
|
+
}
|
package/src/parse.mjs
ADDED
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag';
|
|
2
|
+
import Null from '@muze-nl/jsontag/src/lib/Null.mjs'
|
|
3
|
+
import serialize from './serialize.mjs'
|
|
4
|
+
import {source,isProxy,getBuffer,getIndex,isChanged,isParsed,position,parent,resultSet} from './symbols.mjs'
|
|
5
|
+
|
|
6
|
+
const decoder = new TextDecoder()
|
|
7
|
+
const encoder = new TextEncoder()
|
|
8
|
+
|
|
9
|
+
function stringToSAB(strData) {
|
|
10
|
+
const buffer = encoder.encode(strData)
|
|
11
|
+
const sab = new SharedArrayBuffer(buffer.length)
|
|
12
|
+
let uint8sab = new Uint8Array(sab)
|
|
13
|
+
uint8sab.set(buffer,0)
|
|
14
|
+
return uint8sab
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function parse(input, meta, immutable=true)
|
|
18
|
+
{
|
|
19
|
+
if (!meta) {
|
|
20
|
+
meta = {}
|
|
21
|
+
}
|
|
22
|
+
if (!meta.unresolved) {
|
|
23
|
+
meta.unresolved = new Map()
|
|
24
|
+
}
|
|
25
|
+
if (!meta.baseURL) {
|
|
26
|
+
meta.baseURL = 'http://localhost/'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let at, ch, value, result;
|
|
30
|
+
let escapee = {
|
|
31
|
+
'"': '"',
|
|
32
|
+
"\\":"\\",
|
|
33
|
+
'/': '/',
|
|
34
|
+
b: "\b",
|
|
35
|
+
f: "\f",
|
|
36
|
+
n: "\n",
|
|
37
|
+
r: "\r",
|
|
38
|
+
t: "\t"
|
|
39
|
+
}
|
|
40
|
+
let offsetArray = []
|
|
41
|
+
let resultArray = []
|
|
42
|
+
|
|
43
|
+
at = 0
|
|
44
|
+
ch = " "
|
|
45
|
+
|
|
46
|
+
let error = function(m)
|
|
47
|
+
{
|
|
48
|
+
let context
|
|
49
|
+
try {
|
|
50
|
+
context = decoder.decode(input.slice(at-100,at+100));
|
|
51
|
+
} catch(err) {}
|
|
52
|
+
throw {
|
|
53
|
+
name: 'SyntaxError',
|
|
54
|
+
message: m,
|
|
55
|
+
at: at,
|
|
56
|
+
input: context
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof input == 'string' || input instanceof String) {
|
|
61
|
+
input = stringToSAB(input)
|
|
62
|
+
}
|
|
63
|
+
if (!(input instanceof Uint8Array)) {
|
|
64
|
+
error('parse only accepts Uint8Array or String as input')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let next = function(c)
|
|
68
|
+
{
|
|
69
|
+
if (c && c!==ch) {
|
|
70
|
+
error("Expected '"+c+"' instead of '"+ch+"': "+at+':'+input)
|
|
71
|
+
}
|
|
72
|
+
ch = String.fromCharCode(input.at(at))
|
|
73
|
+
at+=1
|
|
74
|
+
return ch
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let number = function(tagName)
|
|
78
|
+
{
|
|
79
|
+
let numString = ''
|
|
80
|
+
if (ch==='-') {
|
|
81
|
+
numString = '-'
|
|
82
|
+
next('-')
|
|
83
|
+
}
|
|
84
|
+
while(ch>='0' && ch<='9') {
|
|
85
|
+
numString += ch
|
|
86
|
+
next()
|
|
87
|
+
}
|
|
88
|
+
if (ch==='.') {
|
|
89
|
+
numString+='.'
|
|
90
|
+
while(next() && ch >= '0' && ch <= '9') {
|
|
91
|
+
numString += ch
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (ch === 'e' || ch === 'E') {
|
|
95
|
+
numString += ch
|
|
96
|
+
next()
|
|
97
|
+
if (ch === '-' || ch === '+') {
|
|
98
|
+
numString += ch
|
|
99
|
+
next()
|
|
100
|
+
}
|
|
101
|
+
while (ch >= '0' && ch <= '9') {
|
|
102
|
+
numString += ch
|
|
103
|
+
next()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let result = new Number(numString).valueOf()
|
|
107
|
+
if (tagName) {
|
|
108
|
+
switch(tagName) {
|
|
109
|
+
case "int":
|
|
110
|
+
isInt(numString)
|
|
111
|
+
break
|
|
112
|
+
case "uint":
|
|
113
|
+
isInt(numString, [0,Infinity])
|
|
114
|
+
break
|
|
115
|
+
case "int8":
|
|
116
|
+
isInt(numString, [-128,127])
|
|
117
|
+
break
|
|
118
|
+
case "uint8":
|
|
119
|
+
isInt(numString, [0,255])
|
|
120
|
+
break
|
|
121
|
+
case "int16":
|
|
122
|
+
isInt(numString, [-32768,32767])
|
|
123
|
+
break
|
|
124
|
+
case "uint16":
|
|
125
|
+
isInt(numString, [0,65535])
|
|
126
|
+
break
|
|
127
|
+
case "int32":
|
|
128
|
+
isInt(numString, [-2147483648, 2147483647])
|
|
129
|
+
break
|
|
130
|
+
case "uint32":
|
|
131
|
+
isInt(numString, [0,4294967295])
|
|
132
|
+
break
|
|
133
|
+
case "timestamp":
|
|
134
|
+
case "int64":
|
|
135
|
+
isInt(numString, [-9223372036854775808,9223372036854775807])
|
|
136
|
+
break
|
|
137
|
+
case "uint64":
|
|
138
|
+
isInt(numString, [0,18446744073709551615])
|
|
139
|
+
break
|
|
140
|
+
case "float":
|
|
141
|
+
isFloat(numString)
|
|
142
|
+
break
|
|
143
|
+
case "float32":
|
|
144
|
+
isFloat(numString, [-3.4e+38,3.4e+38])
|
|
145
|
+
break
|
|
146
|
+
case "float64":
|
|
147
|
+
isFloat(numString, [-1.7e+308,+1.7e+308])
|
|
148
|
+
break
|
|
149
|
+
case "number":
|
|
150
|
+
//FIXME: what to check? should already be covered by JSON parsing rules?
|
|
151
|
+
break
|
|
152
|
+
default:
|
|
153
|
+
isTypeError(tagName, numString)
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let isTypeError = function(type, value)
|
|
161
|
+
{
|
|
162
|
+
error('Syntax error, expected '+type+', got: '+value)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const regexes = {
|
|
166
|
+
color: /^(rgb|hsl)a?\((\d+%?(deg|rad|grad|turn)?[,\s]+){2,3}[\s\/]*[\d\.]+%?\)$/i,
|
|
167
|
+
email: /^[A-Za-z0-9_!#$%&'*+\/=?`{|}~^.-]+@[A-Za-z0-9.-]+$/,
|
|
168
|
+
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}$/,
|
|
169
|
+
decimal: /^\d*\.?\d*$/,
|
|
170
|
+
money: /^[A-Z]+\$\d*\.?\d*$/,
|
|
171
|
+
duration: /^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/,
|
|
172
|
+
phone: /^[+]?(?:\(\d+(?:\.\d+)?\)|\d+(?:\.\d+)?)(?:[ -]?(?:\(\d+(?:\.\d+)?\)|\d+(?:\.\d+)?))*(?:[ ]?(?:x|ext)\.?[ ]?\d{1,5})?$/,
|
|
173
|
+
time: /^(\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?$/,
|
|
174
|
+
date: /^-?[1-9][0-9]{3,}-([0][1-9]|[1][0-2])-([1-2][0-9]|[0][1-9]|[3][0-1])$/,
|
|
175
|
+
datetime: /^(\d{4,})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?$/,
|
|
176
|
+
range: /^\[-?(\d+\.)?\d+\,-?(\d+\.)?\d+\]$/
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let isFloat = function(float, range)
|
|
180
|
+
{
|
|
181
|
+
let test = new Number(parseFloat(float))
|
|
182
|
+
let str = test.toString()
|
|
183
|
+
if (float!==str) {
|
|
184
|
+
error('Syntax Error: expected float value')
|
|
185
|
+
}
|
|
186
|
+
if (range) {
|
|
187
|
+
if (typeof range[0] === 'number') {
|
|
188
|
+
if (test<range[0]) {
|
|
189
|
+
error('Syntax Error: float value out of range')
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (typeof range[1] === 'number') {
|
|
193
|
+
if (test>range[1]) {
|
|
194
|
+
error('Syntax Error: float value out of range')
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let isInt = function(int, range)
|
|
201
|
+
{
|
|
202
|
+
let test = new Number(parseInt(int))
|
|
203
|
+
let str = test.toString()
|
|
204
|
+
if (int!==str) {
|
|
205
|
+
error('Syntax Error: expected integer value')
|
|
206
|
+
}
|
|
207
|
+
if (range) {
|
|
208
|
+
if (typeof range[0] === 'number') {
|
|
209
|
+
if (test<range[0]) {
|
|
210
|
+
error('Syntax Error: integer value out of range')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (typeof range[1] === 'number') {
|
|
214
|
+
if (test>range[1]) {
|
|
215
|
+
error('Syntax Error: integer value out of range')
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let isColor = function(color)
|
|
222
|
+
{
|
|
223
|
+
let result = false
|
|
224
|
+
if (color.charAt(0) === "#") {
|
|
225
|
+
color = color.substring(1)
|
|
226
|
+
result = ([3, 4, 6, 8].indexOf(color.length) > -1) && !isNaN(parseInt(color, 16))
|
|
227
|
+
if (result.toString(16)!==color) {
|
|
228
|
+
isTypeError('color', color)
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
result = regexes.color.test(color)
|
|
232
|
+
}
|
|
233
|
+
if (!result) {
|
|
234
|
+
isTypeError('color',color)
|
|
235
|
+
}
|
|
236
|
+
return true
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let isEmail = function(email)
|
|
240
|
+
{
|
|
241
|
+
let result = regexes.email.test(email)
|
|
242
|
+
if (!result) {
|
|
243
|
+
isTypeError('email',email)
|
|
244
|
+
}
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let isUuid = function(uuid)
|
|
249
|
+
{
|
|
250
|
+
let result = regexes.uuid.test(uuid)
|
|
251
|
+
if (!result) {
|
|
252
|
+
isTypeError('uuid',uuid)
|
|
253
|
+
}
|
|
254
|
+
return true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let isDecimal = function(decimal)
|
|
258
|
+
{
|
|
259
|
+
let result = regexes.decimal.test(decimal)
|
|
260
|
+
if (!result) {
|
|
261
|
+
isTypeError('decimal',decimal)
|
|
262
|
+
}
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let isMoney = function(money)
|
|
267
|
+
{
|
|
268
|
+
let result = regexes.money.test(money)
|
|
269
|
+
if (!result) {
|
|
270
|
+
isTypeError('money',money)
|
|
271
|
+
}
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let isUrl = function(url)
|
|
276
|
+
{
|
|
277
|
+
try {
|
|
278
|
+
return Boolean(new URL(url, meta.baseURL))
|
|
279
|
+
} catch(e) {
|
|
280
|
+
isTypeError('url',url)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let isDuration = function(duration)
|
|
285
|
+
{
|
|
286
|
+
let result = regexes.duration.test(duration)
|
|
287
|
+
if (!result) {
|
|
288
|
+
isTypeError('duration',duration)
|
|
289
|
+
}
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let isPhone = function(phone)
|
|
294
|
+
{
|
|
295
|
+
let result = regexes.phone.test(phone)
|
|
296
|
+
if (!result) {
|
|
297
|
+
isTypeError('phone',phone)
|
|
298
|
+
}
|
|
299
|
+
return true
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let isRange = function(range)
|
|
303
|
+
{
|
|
304
|
+
let result = regexes.range.test(range)
|
|
305
|
+
if (!result) {
|
|
306
|
+
isTypeError('range',range)
|
|
307
|
+
}
|
|
308
|
+
return true
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let isTime = function(time)
|
|
312
|
+
{
|
|
313
|
+
let result = regexes.time.test(time)
|
|
314
|
+
if (!result) {
|
|
315
|
+
isTypeError('time',time)
|
|
316
|
+
}
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let isDate = function(date)
|
|
321
|
+
{
|
|
322
|
+
let result = regexes.date.test(date)
|
|
323
|
+
if (!result) {
|
|
324
|
+
isTypeError('date',date)
|
|
325
|
+
}
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let isDatetime = function(datetime)
|
|
330
|
+
{
|
|
331
|
+
let result = regexes.datetime.test(datetime)
|
|
332
|
+
if (!result) {
|
|
333
|
+
isTypeError('datetime',datetime)
|
|
334
|
+
}
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let checkStringType = function(tagName, value)
|
|
339
|
+
{
|
|
340
|
+
if (!tagName) {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
switch(tagName){
|
|
344
|
+
case "object":
|
|
345
|
+
case "array":
|
|
346
|
+
case "int8":
|
|
347
|
+
case "uint8":
|
|
348
|
+
case "int16":
|
|
349
|
+
case "uint16":
|
|
350
|
+
case "int32":
|
|
351
|
+
case "uint32":
|
|
352
|
+
case "int64":
|
|
353
|
+
case "uint64":
|
|
354
|
+
case "int":
|
|
355
|
+
case "uint":
|
|
356
|
+
case "float32":
|
|
357
|
+
case "float64":
|
|
358
|
+
case "float":
|
|
359
|
+
case "timestamp":
|
|
360
|
+
isTypeError(tagName, value)
|
|
361
|
+
break
|
|
362
|
+
case "uuid":
|
|
363
|
+
return isUuid(value)
|
|
364
|
+
case "decimal":
|
|
365
|
+
return isDecimal(value)
|
|
366
|
+
case "money":
|
|
367
|
+
return isMoney(value)
|
|
368
|
+
case "url":
|
|
369
|
+
return isUrl(value)
|
|
370
|
+
case "link":
|
|
371
|
+
case "string":
|
|
372
|
+
case "text":
|
|
373
|
+
case "blob":
|
|
374
|
+
case "hash":
|
|
375
|
+
//anything goes
|
|
376
|
+
return true
|
|
377
|
+
case "color":
|
|
378
|
+
return isColor(value)
|
|
379
|
+
case "email":
|
|
380
|
+
return isEmail(value)
|
|
381
|
+
case "duration":
|
|
382
|
+
return isDuration(value)
|
|
383
|
+
case "phone":
|
|
384
|
+
return isPhone(value)
|
|
385
|
+
case "range":
|
|
386
|
+
return isRange(value)
|
|
387
|
+
case "time":
|
|
388
|
+
return isTime(value)
|
|
389
|
+
case "date":
|
|
390
|
+
return isDate(value)
|
|
391
|
+
case "datetime":
|
|
392
|
+
return isDatetime(value)
|
|
393
|
+
}
|
|
394
|
+
error('Syntax error: unknown tagName '+tagName)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let string = function(tagName)
|
|
398
|
+
{
|
|
399
|
+
let value = [], hex, i, uffff;
|
|
400
|
+
if (ch !== '"') {
|
|
401
|
+
error("Syntax Error")
|
|
402
|
+
}
|
|
403
|
+
next('"')
|
|
404
|
+
while(ch) {
|
|
405
|
+
if (ch==='"') {
|
|
406
|
+
next()
|
|
407
|
+
let bytes = new Uint8Array(value)
|
|
408
|
+
value = decoder.decode(bytes)
|
|
409
|
+
checkStringType(tagName, value)
|
|
410
|
+
return value
|
|
411
|
+
}
|
|
412
|
+
if (ch==='\\') {
|
|
413
|
+
next()
|
|
414
|
+
if (ch==='u') {
|
|
415
|
+
for (i=0; i<4; i++) {
|
|
416
|
+
hex = parseInt(next(), 16)
|
|
417
|
+
if (!isFinite(hex)) {
|
|
418
|
+
break
|
|
419
|
+
}
|
|
420
|
+
uffff = uffff * 16 + hex
|
|
421
|
+
}
|
|
422
|
+
let str = String.fromCharCode(uffff)
|
|
423
|
+
let bytes = encoder.encode(str)
|
|
424
|
+
value.push.apply(value, bytes)
|
|
425
|
+
next()
|
|
426
|
+
} else if (typeof escapee[ch] === 'string') {
|
|
427
|
+
value.push(escapee[ch].charCodeAt(0))
|
|
428
|
+
next()
|
|
429
|
+
} else {
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
value.push(ch.charCodeAt(0))
|
|
434
|
+
next()
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
error("Syntax error: incomplete string")
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let tag = function()
|
|
441
|
+
{
|
|
442
|
+
let key, val, tagOb={
|
|
443
|
+
attributes: {}
|
|
444
|
+
}
|
|
445
|
+
if (ch !== '<') {
|
|
446
|
+
error("Syntax Error")
|
|
447
|
+
}
|
|
448
|
+
next('<')
|
|
449
|
+
key = word()
|
|
450
|
+
if (!key) {
|
|
451
|
+
error('Syntax Error: expected tag name')
|
|
452
|
+
}
|
|
453
|
+
tagOb.tagName = key
|
|
454
|
+
whitespace()
|
|
455
|
+
while(ch) {
|
|
456
|
+
if (ch==='>') {
|
|
457
|
+
next('>')
|
|
458
|
+
return tagOb
|
|
459
|
+
}
|
|
460
|
+
key = word()
|
|
461
|
+
if (!key) {
|
|
462
|
+
error('Syntax Error: expected attribute name')
|
|
463
|
+
}
|
|
464
|
+
whitespace()
|
|
465
|
+
next('=')
|
|
466
|
+
whitespace()
|
|
467
|
+
val = string()
|
|
468
|
+
tagOb.attributes[key] = val
|
|
469
|
+
whitespace()
|
|
470
|
+
}
|
|
471
|
+
error('Syntax Error: unexpected end of input')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let whitespace = function()
|
|
475
|
+
{
|
|
476
|
+
while (ch) {
|
|
477
|
+
switch(ch) {
|
|
478
|
+
case ' ':
|
|
479
|
+
case "\t":
|
|
480
|
+
case "\r":
|
|
481
|
+
case "\n":
|
|
482
|
+
next()
|
|
483
|
+
break
|
|
484
|
+
default:
|
|
485
|
+
return
|
|
486
|
+
break
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let word = function()
|
|
492
|
+
{
|
|
493
|
+
//[a-z][a-z0-9_]*
|
|
494
|
+
let val='';
|
|
495
|
+
if ((ch>='a' && ch<='z') || (ch>='A' && ch<='Z')) {
|
|
496
|
+
val += ch
|
|
497
|
+
next()
|
|
498
|
+
} else {
|
|
499
|
+
error('Syntax Error: expected word')
|
|
500
|
+
}
|
|
501
|
+
while((ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || (ch>='0' && ch<='9') || ch=='_') {
|
|
502
|
+
val += ch
|
|
503
|
+
next()
|
|
504
|
+
}
|
|
505
|
+
return val
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let boolOrNull = function(tagName)
|
|
509
|
+
{
|
|
510
|
+
let w = word()
|
|
511
|
+
if (!w || typeof w !== 'string') {
|
|
512
|
+
error('Syntax error: expected boolean or null, got "'+w+'"')
|
|
513
|
+
}
|
|
514
|
+
switch(w.toLowerCase()) {
|
|
515
|
+
case 'true':
|
|
516
|
+
if (tagName && tagName!=='boolean') {
|
|
517
|
+
isTypeError(tagName,w)
|
|
518
|
+
}
|
|
519
|
+
return true
|
|
520
|
+
break
|
|
521
|
+
case 'false':
|
|
522
|
+
if (tagName && tagName!=='boolean') {
|
|
523
|
+
isTypeError(tagName,w)
|
|
524
|
+
}
|
|
525
|
+
return false
|
|
526
|
+
break
|
|
527
|
+
case 'null':
|
|
528
|
+
return null
|
|
529
|
+
break
|
|
530
|
+
default:
|
|
531
|
+
error('Syntax error: expected boolean or null, got "'+w+'"')
|
|
532
|
+
break
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let checkUnresolved = function(item, object, key)
|
|
537
|
+
{
|
|
538
|
+
if (JSONTag.getType(item)==='link') {
|
|
539
|
+
let link = ''+item
|
|
540
|
+
let links = meta.unresolved.get(link)
|
|
541
|
+
if (typeof links === 'undefined') {
|
|
542
|
+
meta.unresolved.set(link,[])
|
|
543
|
+
links = meta.unresolved.get(link)
|
|
544
|
+
}
|
|
545
|
+
let count = links.push({
|
|
546
|
+
src: new WeakRef(object),
|
|
547
|
+
key: key
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let array = function()
|
|
553
|
+
{
|
|
554
|
+
let item, array = []
|
|
555
|
+
if (ch !== '[') {
|
|
556
|
+
error("Syntax error")
|
|
557
|
+
}
|
|
558
|
+
next('[')
|
|
559
|
+
whitespace()
|
|
560
|
+
if (ch===']') {
|
|
561
|
+
next(']')
|
|
562
|
+
return array
|
|
563
|
+
}
|
|
564
|
+
while(ch) {
|
|
565
|
+
item = value()
|
|
566
|
+
checkUnresolved(item, array, array.length)
|
|
567
|
+
array.push(item)
|
|
568
|
+
whitespace()
|
|
569
|
+
if (ch===']') {
|
|
570
|
+
next(']')
|
|
571
|
+
return array
|
|
572
|
+
}
|
|
573
|
+
next(',')
|
|
574
|
+
whitespace()
|
|
575
|
+
}
|
|
576
|
+
error("Input stopped early")
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
let object = function(object={})
|
|
580
|
+
{
|
|
581
|
+
let key, val
|
|
582
|
+
if (ch !== '{') {
|
|
583
|
+
error("Syntax Error")
|
|
584
|
+
}
|
|
585
|
+
next('{')
|
|
586
|
+
whitespace()
|
|
587
|
+
if (ch==='}') {
|
|
588
|
+
next('}')
|
|
589
|
+
return object
|
|
590
|
+
}
|
|
591
|
+
let enumerable = true
|
|
592
|
+
while(ch) {
|
|
593
|
+
if (ch==='#') {
|
|
594
|
+
enumerable = false
|
|
595
|
+
next()
|
|
596
|
+
} else {
|
|
597
|
+
enumerable = true
|
|
598
|
+
}
|
|
599
|
+
key = string()
|
|
600
|
+
if (key==='__proto__') {
|
|
601
|
+
error("Attempt at prototype pollution")
|
|
602
|
+
}
|
|
603
|
+
whitespace()
|
|
604
|
+
next(':')
|
|
605
|
+
val = value()
|
|
606
|
+
if (!enumerable) {
|
|
607
|
+
Object.defineProperty(object, key, {
|
|
608
|
+
configurable: true, //important, must be true, otherwise Proxies cannot use it
|
|
609
|
+
writable: true, // handle immutability in the Proxy traps
|
|
610
|
+
enumerable: false,
|
|
611
|
+
value: val
|
|
612
|
+
})
|
|
613
|
+
} else {
|
|
614
|
+
object[key] = val
|
|
615
|
+
}
|
|
616
|
+
checkUnresolved(val, object, key)
|
|
617
|
+
whitespace()
|
|
618
|
+
if (ch==='}') {
|
|
619
|
+
next('}')
|
|
620
|
+
return object
|
|
621
|
+
}
|
|
622
|
+
next(',')
|
|
623
|
+
whitespace()
|
|
624
|
+
}
|
|
625
|
+
error("Input stopped early")
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let length = function()
|
|
629
|
+
{
|
|
630
|
+
whitespace()
|
|
631
|
+
next('(')
|
|
632
|
+
let numString=''
|
|
633
|
+
while(ch>='0' && ch<='9') {
|
|
634
|
+
numString += ch
|
|
635
|
+
next()
|
|
636
|
+
}
|
|
637
|
+
if (ch!==')') {
|
|
638
|
+
error('Syntax error: not a length')
|
|
639
|
+
}
|
|
640
|
+
next()
|
|
641
|
+
return parseInt(numString)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
let offset = function()
|
|
645
|
+
{
|
|
646
|
+
next('~')
|
|
647
|
+
let numString = ''
|
|
648
|
+
while(ch>='0' && ch<='9') {
|
|
649
|
+
numString += ch
|
|
650
|
+
next()
|
|
651
|
+
}
|
|
652
|
+
return parseInt(numString)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
let parseValue = function(position, ob={}) {
|
|
656
|
+
at = position.start
|
|
657
|
+
next()
|
|
658
|
+
return value(ob)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const makeChildProxies = function(parent) {
|
|
662
|
+
Object.entries(parent).forEach(([key,entry]) => {
|
|
663
|
+
if (Array.isArray(entry)) {
|
|
664
|
+
makeChildProxies(entry)
|
|
665
|
+
} else if (JSONTag.getType(entry)==='object') {
|
|
666
|
+
if (entry[isProxy]) {
|
|
667
|
+
// do nothing
|
|
668
|
+
} else {
|
|
669
|
+
parent[key] = getNewValueProxy(entry)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const handlers = {
|
|
676
|
+
newArrayHandler: {
|
|
677
|
+
get(target, prop) {
|
|
678
|
+
if (target[prop] instanceof Function) {
|
|
679
|
+
return (...args) => {
|
|
680
|
+
args = args.map(arg => {
|
|
681
|
+
if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
|
|
682
|
+
arg = getNewValueProxy(arg)
|
|
683
|
+
}
|
|
684
|
+
return arg
|
|
685
|
+
})
|
|
686
|
+
return target[prop].apply(target, args)
|
|
687
|
+
}
|
|
688
|
+
} else if (prop===isChanged) {
|
|
689
|
+
return true
|
|
690
|
+
} else {
|
|
691
|
+
if (Array.isArray(target[prop])) {
|
|
692
|
+
return new Proxy(target[prop], handlers.newArrayHandler)
|
|
693
|
+
}
|
|
694
|
+
return target[prop]
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
set(target, prop, value) {
|
|
698
|
+
if (JSONTag.getType(value)==='object' && !value[isProxy]) {
|
|
699
|
+
value = getNewValueProxy(value)
|
|
700
|
+
}
|
|
701
|
+
target[prop] = value
|
|
702
|
+
return true
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
newValueHandler: {
|
|
706
|
+
get(target, prop, receiver) {
|
|
707
|
+
switch(prop) {
|
|
708
|
+
case source:
|
|
709
|
+
return target
|
|
710
|
+
break
|
|
711
|
+
case isProxy:
|
|
712
|
+
return true
|
|
713
|
+
break
|
|
714
|
+
case getBuffer:
|
|
715
|
+
return (i) => {
|
|
716
|
+
let index = target[getIndex]
|
|
717
|
+
if (i != index) {
|
|
718
|
+
return encoder.encode('~'+index)
|
|
719
|
+
}
|
|
720
|
+
return serialize(target, meta, true, i)
|
|
721
|
+
}
|
|
722
|
+
break
|
|
723
|
+
case getIndex:
|
|
724
|
+
return target[getIndex]
|
|
725
|
+
break
|
|
726
|
+
case isChanged:
|
|
727
|
+
return true
|
|
728
|
+
break
|
|
729
|
+
default:
|
|
730
|
+
if (Array.isArray(target[prop])) {
|
|
731
|
+
return new Proxy(target[prop], handlers.newArrayHandler)
|
|
732
|
+
}
|
|
733
|
+
return target[prop]
|
|
734
|
+
break
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
set(target, prop, value) {
|
|
738
|
+
if (JSONTag.getType(value)==='object' && !value[isProxy]) {
|
|
739
|
+
value = getNewValueProxy(value)
|
|
740
|
+
}
|
|
741
|
+
target[prop] = value
|
|
742
|
+
return true
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
arrayHandler: {
|
|
746
|
+
get(target, prop) {
|
|
747
|
+
if (target[prop] instanceof Function) {
|
|
748
|
+
if (['copyWithin','fill','pop','push','reverse','shift','sort','splice','unshift'].indexOf(prop)!==-1) {
|
|
749
|
+
if (immutable) {
|
|
750
|
+
throw new Error('dataspace is immutable')
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return (...args) => {
|
|
754
|
+
args = args.map(arg => {
|
|
755
|
+
if (JSONTag.getType(arg)==='object' && !arg[isProxy]) {
|
|
756
|
+
arg = getNewValueProxy(arg)
|
|
757
|
+
}
|
|
758
|
+
return arg
|
|
759
|
+
})
|
|
760
|
+
target[parent][isChanged] = true // incorrect target for isChanged...
|
|
761
|
+
let result = target[prop].apply(target, args)
|
|
762
|
+
return result
|
|
763
|
+
}
|
|
764
|
+
} else if (prop===isChanged) {
|
|
765
|
+
return target[parent][isChanged]
|
|
766
|
+
} else {
|
|
767
|
+
if (Array.isArray(target[prop])) {
|
|
768
|
+
target[prop][parent] = target[parent]
|
|
769
|
+
return new Proxy(target[prop], handlers.arrayHandler)
|
|
770
|
+
}
|
|
771
|
+
return target[prop]
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
set(target, prop, value) {
|
|
775
|
+
if (immutable) {
|
|
776
|
+
throw new Error('dataspace is immutable')
|
|
777
|
+
}
|
|
778
|
+
if (JSONTag.getType(value)==='object' && !value[isProxy]) {
|
|
779
|
+
value = getNewValueProxy(value)
|
|
780
|
+
}
|
|
781
|
+
target[prop] = value
|
|
782
|
+
target[parent][isChanged] = true
|
|
783
|
+
return true
|
|
784
|
+
},
|
|
785
|
+
deleteProperty(target, prop) {
|
|
786
|
+
if (immutable) {
|
|
787
|
+
throw new Error('dataspace is immutable')
|
|
788
|
+
}
|
|
789
|
+
//FIXME: if target[prop] was the last reference to an object
|
|
790
|
+
//that object should be deleted so that its line will become empty
|
|
791
|
+
//when stringifying resultArray again
|
|
792
|
+
delete target[prop]
|
|
793
|
+
target[parent][isChanged] = true
|
|
794
|
+
return true
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
handler: {
|
|
798
|
+
get(target, prop, receiver) {
|
|
799
|
+
firstParse(target)
|
|
800
|
+
switch(prop) {
|
|
801
|
+
case source:
|
|
802
|
+
return target
|
|
803
|
+
break
|
|
804
|
+
case isProxy:
|
|
805
|
+
return true
|
|
806
|
+
break
|
|
807
|
+
case getBuffer:
|
|
808
|
+
return (i) => {
|
|
809
|
+
let index = target[getIndex]
|
|
810
|
+
if (i != index) {
|
|
811
|
+
return encoder.encode('~'+index)
|
|
812
|
+
}
|
|
813
|
+
if (target[isChanged]) {
|
|
814
|
+
return serialize(target, null, true)
|
|
815
|
+
}
|
|
816
|
+
return input.slice(target[position].start,target[position].end)
|
|
817
|
+
}
|
|
818
|
+
break
|
|
819
|
+
case getIndex:
|
|
820
|
+
return target[getIndex]
|
|
821
|
+
break
|
|
822
|
+
case isChanged:
|
|
823
|
+
return target[isChanged]
|
|
824
|
+
break
|
|
825
|
+
default:
|
|
826
|
+
if (Array.isArray(target[prop])) {
|
|
827
|
+
target[prop][parent] = target
|
|
828
|
+
return new Proxy(target[prop], handlers.arrayHandler)
|
|
829
|
+
}
|
|
830
|
+
return target[prop]
|
|
831
|
+
break
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
set(target, prop, value) {
|
|
835
|
+
if (immutable && prop!==resultSet) {
|
|
836
|
+
throw new Error('dataspace is immutable')
|
|
837
|
+
}
|
|
838
|
+
firstParse(target)
|
|
839
|
+
if (prop!==isChanged) {
|
|
840
|
+
if (JSONTag.getType(value)==='object' && !value[isProxy]) {
|
|
841
|
+
value = getNewValueProxy(value)
|
|
842
|
+
}
|
|
843
|
+
target[prop] = value
|
|
844
|
+
}
|
|
845
|
+
target[isChanged] = true
|
|
846
|
+
return true
|
|
847
|
+
},
|
|
848
|
+
deleteProperty(target, prop) {
|
|
849
|
+
if (immutable) {
|
|
850
|
+
throw new Error('dataspace is immutable')
|
|
851
|
+
}
|
|
852
|
+
firstParse(target)
|
|
853
|
+
delete target[prop]
|
|
854
|
+
target[isChanged] = true
|
|
855
|
+
return true
|
|
856
|
+
},
|
|
857
|
+
ownKeys(target) {
|
|
858
|
+
firstParse(target)
|
|
859
|
+
return Reflect.ownKeys(target)
|
|
860
|
+
},
|
|
861
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
862
|
+
firstParse(target)
|
|
863
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
864
|
+
},
|
|
865
|
+
defineProperty(target, prop, descriptor) {
|
|
866
|
+
if (immutable) {
|
|
867
|
+
throw new Error('dataspace is immutable')
|
|
868
|
+
}
|
|
869
|
+
firstParse(target)
|
|
870
|
+
Object.defineProperty(target, prop, descriptor)
|
|
871
|
+
},
|
|
872
|
+
has(target, prop) {
|
|
873
|
+
firstParse()
|
|
874
|
+
return prop in target
|
|
875
|
+
},
|
|
876
|
+
setPrototypeOf(target,proto) {
|
|
877
|
+
throw new Error('changing prototypes is not supported')
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const firstParse = function(target) {
|
|
883
|
+
if (!target[isParsed]) {
|
|
884
|
+
parseValue(target[position], target)
|
|
885
|
+
target[isParsed] = true
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const getNewValueProxy = function(value) {
|
|
890
|
+
let index = resultArray.length
|
|
891
|
+
resultArray.push('')
|
|
892
|
+
value[getIndex] = index
|
|
893
|
+
makeChildProxies(value)
|
|
894
|
+
let result = new Proxy(value, handlers.newValueHandler)
|
|
895
|
+
resultArray[index] = result
|
|
896
|
+
return result
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
let valueProxy = function(length, index)
|
|
900
|
+
{
|
|
901
|
+
let cache = {}
|
|
902
|
+
cache[getIndex] = index
|
|
903
|
+
cache[isChanged] = false
|
|
904
|
+
cache[isParsed] = false
|
|
905
|
+
// current offset + length contains jsontag of this value
|
|
906
|
+
cache[position] = {
|
|
907
|
+
start: at-1,
|
|
908
|
+
end: at-1+length
|
|
909
|
+
}
|
|
910
|
+
at += length
|
|
911
|
+
next()
|
|
912
|
+
// newValueHandler makes sure that value[getBuffer] runs stringify
|
|
913
|
+
// arrayHandler makes sure that changes in the array set targetIsChanged to true
|
|
914
|
+
return new Proxy(cache, handlers.handler)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
value = function(ob={})
|
|
918
|
+
{
|
|
919
|
+
let tagOb, result, tagName;
|
|
920
|
+
whitespace()
|
|
921
|
+
if (ch==='~') {
|
|
922
|
+
let vOffset = offset()
|
|
923
|
+
return resultArray[vOffset]
|
|
924
|
+
}
|
|
925
|
+
if (ch==='<') {
|
|
926
|
+
tagOb = tag()
|
|
927
|
+
tagName = tagOb.tagName
|
|
928
|
+
whitespace()
|
|
929
|
+
}
|
|
930
|
+
switch(ch) {
|
|
931
|
+
case '{':
|
|
932
|
+
if (tagName && tagName!=='object') {
|
|
933
|
+
isTypeError(tagName, ch)
|
|
934
|
+
}
|
|
935
|
+
result = object(ob)
|
|
936
|
+
break
|
|
937
|
+
case '[':
|
|
938
|
+
if (tagName && tagName!=='array') {
|
|
939
|
+
isTypeError(tagName, ch)
|
|
940
|
+
}
|
|
941
|
+
result = array()
|
|
942
|
+
break
|
|
943
|
+
case '"':
|
|
944
|
+
result = string(tagName)
|
|
945
|
+
break
|
|
946
|
+
case '-':
|
|
947
|
+
result = number(tagName)
|
|
948
|
+
break
|
|
949
|
+
default:
|
|
950
|
+
if (ch>='0' && ch<='9') {
|
|
951
|
+
result = number(tagName)
|
|
952
|
+
} else {
|
|
953
|
+
result = boolOrNull(tagName)
|
|
954
|
+
}
|
|
955
|
+
break
|
|
956
|
+
}
|
|
957
|
+
if (tagOb) {
|
|
958
|
+
if (result === null) {
|
|
959
|
+
result = new Null()
|
|
960
|
+
}
|
|
961
|
+
if (typeof result !== 'object') {
|
|
962
|
+
switch(typeof result) {
|
|
963
|
+
case 'string':
|
|
964
|
+
result = new String(result)
|
|
965
|
+
break
|
|
966
|
+
case 'number':
|
|
967
|
+
result = new Number(result)
|
|
968
|
+
break
|
|
969
|
+
default:
|
|
970
|
+
error('Syntax Error: unexpected type '+(typeof result))
|
|
971
|
+
break
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (tagOb.tagName) {
|
|
975
|
+
JSONTag.setType(result, tagOb.tagName)
|
|
976
|
+
}
|
|
977
|
+
if (tagOb.attributes) {
|
|
978
|
+
JSONTag.setAttributes(result, tagOb.attributes)
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return result
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function lengthValue(i) {
|
|
985
|
+
let l = length()
|
|
986
|
+
let v = valueProxy(l,i)
|
|
987
|
+
return [l, v]
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
while(ch && at<input.length) {
|
|
991
|
+
result = lengthValue(resultArray.length)
|
|
992
|
+
whitespace()
|
|
993
|
+
offsetArray.push(at)
|
|
994
|
+
resultArray.push(result[1])
|
|
995
|
+
}
|
|
996
|
+
resultArray[0][resultSet] = resultArray
|
|
997
|
+
return resultArray[0]
|
|
998
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import JSONTag from '@muze-nl/jsontag';
|
|
2
|
+
import {source,isProxy, isChanged, getIndex, getBuffer, resultSet} from './symbols.mjs'
|
|
3
|
+
import * as odJSONTag from './jsontag.mjs'
|
|
4
|
+
|
|
5
|
+
// faststringify function for a fast parseable arraybuffer output
|
|
6
|
+
//
|
|
7
|
+
const encoder = new TextEncoder()
|
|
8
|
+
const decoder = new TextDecoder()
|
|
9
|
+
|
|
10
|
+
function stringToSAB(strData) {
|
|
11
|
+
const buffer = encoder.encode(strData)
|
|
12
|
+
const sab = new SharedArrayBuffer(buffer.length)
|
|
13
|
+
let uint8sab = new Uint8Array(sab)
|
|
14
|
+
uint8sab.set(buffer,0)
|
|
15
|
+
return uint8sab
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function serialize(value, meta, skipLength=false, index=false) {
|
|
19
|
+
let resultArray = []
|
|
20
|
+
if (!meta) {
|
|
21
|
+
meta = {}
|
|
22
|
+
}
|
|
23
|
+
if (!meta.index) {
|
|
24
|
+
meta.index = {}
|
|
25
|
+
}
|
|
26
|
+
if (!meta.index.id) {
|
|
27
|
+
meta.index.id = new Map()
|
|
28
|
+
}
|
|
29
|
+
let references = new WeakMap()
|
|
30
|
+
|
|
31
|
+
function stringifyValue(value) {
|
|
32
|
+
let prop
|
|
33
|
+
let typeString = odJSONTag.getTypeString(value)
|
|
34
|
+
let type = odJSONTag.getType(value)
|
|
35
|
+
switch (type) {
|
|
36
|
+
case 'string':
|
|
37
|
+
case 'decimal':
|
|
38
|
+
case 'money':
|
|
39
|
+
case 'link':
|
|
40
|
+
case 'text':
|
|
41
|
+
case 'blob':
|
|
42
|
+
case 'color':
|
|
43
|
+
case 'email':
|
|
44
|
+
case 'hash':
|
|
45
|
+
case 'duration':
|
|
46
|
+
case 'phone':
|
|
47
|
+
case 'url':
|
|
48
|
+
case 'uuid':
|
|
49
|
+
case 'date':
|
|
50
|
+
case 'time':
|
|
51
|
+
case 'datetime':
|
|
52
|
+
if (odJSONTag.isNull(value)) {
|
|
53
|
+
value = 'null'
|
|
54
|
+
} else {
|
|
55
|
+
value = JSON.stringify(''+value)
|
|
56
|
+
}
|
|
57
|
+
prop = typeString + value
|
|
58
|
+
break
|
|
59
|
+
case 'int':
|
|
60
|
+
case 'uint':
|
|
61
|
+
case 'int8':
|
|
62
|
+
case 'uint8':
|
|
63
|
+
case 'int16':
|
|
64
|
+
case 'uint16':
|
|
65
|
+
case 'int32':
|
|
66
|
+
case 'uint32':
|
|
67
|
+
case 'int64':
|
|
68
|
+
case 'uint64':
|
|
69
|
+
case 'float':
|
|
70
|
+
case 'float32':
|
|
71
|
+
case 'float64':
|
|
72
|
+
case 'timestamp':
|
|
73
|
+
case 'number':
|
|
74
|
+
case 'boolean':
|
|
75
|
+
if (odJSONTag.isNull(value)) {
|
|
76
|
+
value = 'null'
|
|
77
|
+
} else {
|
|
78
|
+
value = JSON.stringify(value)
|
|
79
|
+
}
|
|
80
|
+
prop = typeString + value
|
|
81
|
+
break
|
|
82
|
+
case 'array':
|
|
83
|
+
let entries = value.map(e => stringifyValue(e)).join(',')
|
|
84
|
+
prop = typeString + '[' + entries + ']'
|
|
85
|
+
break
|
|
86
|
+
case 'object':
|
|
87
|
+
if (!value) {
|
|
88
|
+
prop = 'null'
|
|
89
|
+
} else if (value[isProxy]) {
|
|
90
|
+
prop = decoder.decode(value[getBuffer](current))
|
|
91
|
+
} else {
|
|
92
|
+
if (!references.has(value)) {
|
|
93
|
+
references.set(value, resultArray.length)
|
|
94
|
+
resultArray.push(value)
|
|
95
|
+
}
|
|
96
|
+
prop = '~'+references.get(value)
|
|
97
|
+
}
|
|
98
|
+
break
|
|
99
|
+
default:
|
|
100
|
+
throw new Error(JSONTag.getType(value)+' type not yet implemented')
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
return prop
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const encoder = new TextEncoder()
|
|
107
|
+
const decoder = new TextDecoder()
|
|
108
|
+
|
|
109
|
+
// is only ever called on object values
|
|
110
|
+
// and should always return a stringified object, not a reference (~n)
|
|
111
|
+
const innerStringify = (current) => {
|
|
112
|
+
let object = resultArray[current]
|
|
113
|
+
let result
|
|
114
|
+
|
|
115
|
+
// if value is a valueProxy, just copy the input slice
|
|
116
|
+
if (object && !odJSONTag.isNull(object) && object[isProxy] && !object[isChanged]) {
|
|
117
|
+
return decoder.decode(object[getBuffer](current))
|
|
118
|
+
}
|
|
119
|
+
if (typeof object === 'undefined' || object === null) {
|
|
120
|
+
return 'null'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let props = []
|
|
124
|
+
for (let key of Object.getOwnPropertyNames(object)) {
|
|
125
|
+
let value = object[key]
|
|
126
|
+
let prop = stringifyValue(value)
|
|
127
|
+
let enumerable = object.propertyIsEnumerable(key) ? '' : '#'
|
|
128
|
+
props.push(enumerable+'"'+key+'":'+prop)
|
|
129
|
+
}
|
|
130
|
+
result = odJSONTag.getTypeString(object)+'{'+props.join(',')+'}'
|
|
131
|
+
return result
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const encode = (s) => {
|
|
135
|
+
if (typeof s == 'string' || s instanceof String) {
|
|
136
|
+
s = encoder.encode(s)
|
|
137
|
+
}
|
|
138
|
+
if (skipLength) {
|
|
139
|
+
return new Uint8Array(s)
|
|
140
|
+
}
|
|
141
|
+
let length = encoder.encode('('+s.length+')')
|
|
142
|
+
let u8arr = new Uint8Array(length.length+s.length)
|
|
143
|
+
u8arr.set(length, 0)
|
|
144
|
+
u8arr.set(s, length.length)
|
|
145
|
+
return u8arr
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (value[resultSet]) {
|
|
149
|
+
resultArray = value[resultSet].slice()
|
|
150
|
+
} else {
|
|
151
|
+
resultArray.push(value)
|
|
152
|
+
}
|
|
153
|
+
let current = 0
|
|
154
|
+
while(current<resultArray.length) {
|
|
155
|
+
if (resultArray[current][isChanged] || !resultArray[current][isProxy]) {
|
|
156
|
+
resultArray[current] = encoder.encode(innerStringify(current))
|
|
157
|
+
} else {
|
|
158
|
+
resultArray[current] = resultArray[current][getBuffer](current)
|
|
159
|
+
}
|
|
160
|
+
current++
|
|
161
|
+
}
|
|
162
|
+
let arr = resultArray.map(encode)
|
|
163
|
+
let length = 0
|
|
164
|
+
for (let line of arr) {
|
|
165
|
+
length += line.length+1
|
|
166
|
+
}
|
|
167
|
+
length -= 1 // skip last newline
|
|
168
|
+
let sab = new SharedArrayBuffer(length)
|
|
169
|
+
let u8arr = new Uint8Array(sab)
|
|
170
|
+
let offset = 0
|
|
171
|
+
for(let line of arr) {
|
|
172
|
+
u8arr.set(line, offset)
|
|
173
|
+
offset+=line.length
|
|
174
|
+
if (offset<length) {
|
|
175
|
+
u8arr.set([10], offset)
|
|
176
|
+
offset++
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return u8arr
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function stringify(buf) {
|
|
183
|
+
return decoder.decode(buf)
|
|
184
|
+
}
|
package/src/symbols.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const source = Symbol('source')
|
|
2
|
+
export const isProxy = Symbol('isProxy')
|
|
3
|
+
export const getBuffer = Symbol('getBuffer')
|
|
4
|
+
export const getIndex = Symbol('getIndex')
|
|
5
|
+
export const isChanged = Symbol('isChanged')
|
|
6
|
+
export const isParsed = Symbol('isParsed')
|
|
7
|
+
export const getString = Symbol('getString')
|
|
8
|
+
export const position = Symbol('position')
|
|
9
|
+
export const parent = Symbol('parent')
|
|
10
|
+
export const resultSet = Symbol('resultSet')
|