json-6 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -18
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/import.d.mts +2 -0
- package/dist/import.d.mts.map +1 -0
- package/dist/index-esm.d.mts +3 -0
- package/dist/index-esm.d.mts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3283 -2120
- package/dist/index.min.js +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.mjs +1706 -1615
- package/dist/json6.d.ts +155 -0
- package/dist/json6.d.ts.map +1 -0
- package/dist/register.d.mts +2 -0
- package/dist/register.d.mts.map +1 -0
- package/dist/require.d.ts +3 -0
- package/dist/require.d.ts.map +1 -0
- package/lib/globals.d.ts +7 -0
- package/lib/import.mjs +30 -82
- package/lib/index-esm.mjs +3 -0
- package/lib/json6.js +665 -222
- package/lib/register.mjs +11 -0
- package/lib/require.js +6 -0
- package/package.json +53 -9
package/lib/json6.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
// GENERATED FILE -- do not edit.
|
|
2
|
+
// Built from src/json6.js by build/src-to-lib.js (npm run build); edits here are overwritten.
|
|
1
3
|
// json6.js
|
|
2
4
|
// JSON for Humans. See README.md for details.
|
|
3
5
|
//
|
|
4
6
|
// This file is based off of https://github.com/d3x0r/sack ./src/netlib/html5.websocket/json6_parser.c
|
|
5
7
|
//
|
|
6
8
|
"use strict";
|
|
7
|
-
const version = "1.1.
|
|
9
|
+
const version = "1.1.5";
|
|
8
10
|
const VALUE_UNDEFINED = -1;
|
|
9
11
|
const VALUE_UNSET = 0;
|
|
10
12
|
const VALUE_NULL = 1;
|
|
@@ -60,33 +62,121 @@ const CONTEXT_IN_ARRAY = 1;
|
|
|
60
62
|
const CONTEXT_OBJECT_FIELD = 3;
|
|
61
63
|
const CONTEXT_OBJECT_FIELD_VALUE = 4;
|
|
62
64
|
|
|
65
|
+
// Matches an ECMAScript `IdentifierName` (the production that may appear
|
|
66
|
+
// unquoted as an object-literal property key in JavaScript). Used only by the
|
|
67
|
+
// opt-in `esStrictCompatible` mode to reject unquoted keys JavaScript would
|
|
68
|
+
// not accept.
|
|
69
|
+
const ES_IDENTIFIER_NAME = /^[$_\p{ID_Start}][$_\p{ID_Continue}]*$/u;
|
|
70
|
+
|
|
71
|
+
// Matches a NumericLiteralSeparator ('_') only where it is legal: between two
|
|
72
|
+
// digits. Used only by `esStrictCompatible` to recognize unquoted numeric keys
|
|
73
|
+
// like `1_000` as valid JS -- `Number()` itself rejects the underscore.
|
|
74
|
+
const NUMERIC_SEPARATOR = /(\d)_(?=\d)/g;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {{
|
|
78
|
+
* n: number,
|
|
79
|
+
* buf: null|string
|
|
80
|
+
* }} Json6Node
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Options accepted by {@link JSON6.parse} and {@link JSON6.begin}. All are
|
|
85
|
+
* off by default: with no options JSON6 accepts its full historical grammar.
|
|
86
|
+
*
|
|
87
|
+
* @typedef {object} Json6ParseOptions
|
|
88
|
+
* @property {boolean} [esStrictCompatible] When `true`, reject the JSON6
|
|
89
|
+
* extensions that are not valid in ECMAScript strict mode, so accepted
|
|
90
|
+
* input can be pasted into a module or `eval`'d unchanged: legacy-octal /
|
|
91
|
+
* leading-zero numbers (`0123`); legacy octal string escapes (`"\012"`,
|
|
92
|
+
* `"\1"`..`"\9"`); literal line terminators inside `'`/`"` strings
|
|
93
|
+
* (backtick strings still allow them); backtick-quoted object keys;
|
|
94
|
+
* unquoted object keys that are neither identifiers nor numbers; and two or
|
|
95
|
+
* more consecutive unary `+`/`-` signs. Defaults to `false`.
|
|
96
|
+
* @property {boolean} [forbidTemplateSubstitution] When `true`, reject an
|
|
97
|
+
* unescaped `${` inside a backtick-quoted string, so a document cannot
|
|
98
|
+
* silently turn into a template-literal substitution if later evaluated as
|
|
99
|
+
* JavaScript. Independent of `esStrictCompatible`. Defaults to `false`.
|
|
100
|
+
* @property {boolean} [warnWithCommentWithoutEOL] When `true`, a `//` comment
|
|
101
|
+
* that reaches the end of the document with no terminating line break logs
|
|
102
|
+
* a console warning. The input is accepted either way. Defaults to `false`.
|
|
103
|
+
* @property {boolean} [normalizeNumericKeys] When `true`, an unquoted object
|
|
104
|
+
* key that parses as a number is replaced with `String(Number(key))`, so
|
|
105
|
+
* `{1e3: true}` gets the key `"1000"` instead of the source text `"1e3"` --
|
|
106
|
+
* matching what a real JS object literal would produce. Independent of
|
|
107
|
+
* `esStrictCompatible` (does not require it). Note: `Number()` parses a
|
|
108
|
+
* leading-zero digit string like `"0123"` as decimal (123), while a real JS
|
|
109
|
+
* numeric literal `0123` is legacy octal (83) in sloppy mode; combine with
|
|
110
|
+
* `esStrictCompatible` (which forbids leading-zero numbers outright) for a
|
|
111
|
+
* guaranteed match. Defaults to `false`.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @template T
|
|
116
|
+
* @typedef {object} NodeInfo
|
|
117
|
+
* @property {T} node
|
|
118
|
+
* @property {NodeInfo<T>|null|undefined} next
|
|
119
|
+
* @property {NodeInfo<T>|null} prior
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @typedef {object} Context
|
|
124
|
+
* @property {0|1|3|4} context
|
|
125
|
+
* @property {Record<string, unknown>|null|undefined} elements
|
|
126
|
+
* @property {unknown[]|null} element_array
|
|
127
|
+
* @property {string|null} name
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @type {Context[]}
|
|
132
|
+
*/
|
|
63
133
|
const contexts = [];
|
|
64
134
|
function getContext() {
|
|
65
|
-
return contexts.pop() || {
|
|
135
|
+
return /** @type {Context} */ (contexts.pop()) || /** @type {Context} */ ({
|
|
66
136
|
context : CONTEXT_UNKNOWN,
|
|
67
137
|
elements : null,
|
|
68
|
-
element_array : null
|
|
69
|
-
|
|
138
|
+
element_array : null,
|
|
139
|
+
name : null
|
|
140
|
+
});
|
|
70
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {Context} ctx
|
|
145
|
+
*/
|
|
71
146
|
function dropContext(ctx) {
|
|
72
147
|
contexts.push( ctx );
|
|
73
148
|
}
|
|
74
149
|
|
|
150
|
+
/** @type {Json6Node[]} */
|
|
75
151
|
const buffers = [];
|
|
76
152
|
function getBuffer() {
|
|
153
|
+
/** @type {Json6Node|undefined} */
|
|
77
154
|
let buf = buffers.pop();
|
|
78
155
|
if( !buf ) buf = { buf:null, n:0 };
|
|
79
156
|
else buf.n = 0;
|
|
80
|
-
return buf;
|
|
157
|
+
return /** @type {Json6Node} */ (buf);
|
|
81
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {Json6Node} buf
|
|
162
|
+
*/
|
|
82
163
|
function dropBuffer(buf) {
|
|
83
164
|
buffers.push( buf );
|
|
84
165
|
}
|
|
85
166
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
167
|
+
/**
|
|
168
|
+
* @template T
|
|
169
|
+
* @param {T|undefined} value
|
|
170
|
+
* @param {string} [msg]
|
|
171
|
+
* @returns {T}
|
|
172
|
+
*/
|
|
173
|
+
function ensureDefined(value, msg) {
|
|
174
|
+
/* istanbul ignore next -- every call site checks buf.length before consuming a code point, so codePointAt never actually returns undefined here */
|
|
175
|
+
if( value === undefined ) throw new Error( msg || "Unexpected undefined value" );
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const JSON6 = {};
|
|
90
180
|
|
|
91
181
|
/*
|
|
92
182
|
let _DEBUG_LL = true;
|
|
@@ -107,6 +197,10 @@ const log = function(type) {
|
|
|
107
197
|
};
|
|
108
198
|
*/
|
|
109
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @param {string} string
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
110
204
|
JSON6.escape = function(string) {
|
|
111
205
|
let output = '';
|
|
112
206
|
if( !string ) return string;
|
|
@@ -120,33 +214,56 @@ JSON6.escape = function(string) {
|
|
|
120
214
|
return output;
|
|
121
215
|
};
|
|
122
216
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
217
|
+
/**
|
|
218
|
+
* @callback Json6Reviver
|
|
219
|
+
* @param {string} key
|
|
220
|
+
* @param {unknown} value
|
|
221
|
+
* @returns {unknown}
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {(value: unknown) => void} [cb]
|
|
226
|
+
* @param {Json6Reviver|undefined} [reviver]
|
|
227
|
+
* @param {Json6ParseOptions} [options]
|
|
228
|
+
*/
|
|
229
|
+
JSON6.begin = function( cb, reviver, options ) {
|
|
230
|
+
|
|
231
|
+
const beginOptions = options;
|
|
232
|
+
/** @type {boolean} */
|
|
233
|
+
let esStrictCompatible = !!( beginOptions && beginOptions.esStrictCompatible );
|
|
234
|
+
/** @type {boolean} */
|
|
235
|
+
let forbidTemplateSubstitution = !!( beginOptions && beginOptions.forbidTemplateSubstitution );
|
|
236
|
+
/** @type {boolean} */
|
|
237
|
+
let warnWithCommentWithoutEOL = !!( beginOptions && beginOptions.warnWithCommentWithoutEOL );
|
|
238
|
+
/** @type {boolean} */
|
|
239
|
+
let normalizeNumericKeys = !!( beginOptions && beginOptions.normalizeNumericKeys );
|
|
240
|
+
|
|
241
|
+
const val = /** @type {{ name: string|null, value_type: number, string: string, contains: unknown }} */ ({ name : null, // name of this value (if it's contained in an object)
|
|
127
242
|
value_type: VALUE_UNSET, // value from above indiciating the type of this value
|
|
128
243
|
string : '', // the string value of this value (strings and number types only)
|
|
129
244
|
contains : null,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let
|
|
245
|
+
});
|
|
246
|
+
// col is incremented as each character is taken, so it starts at 0 and the first
|
|
247
|
+
// character of a line reports as column 1.
|
|
248
|
+
const pos = /** @type {{ line: number, col: number }} */ ({ line:1, col:0 });
|
|
249
|
+
/** @type {number} */ let n = 0;
|
|
250
|
+
/** @type {number} */ let word = WORD_POS_RESET,
|
|
135
251
|
status = true,
|
|
136
252
|
negative = false,
|
|
137
|
-
result = null,
|
|
138
|
-
elements = undefined,
|
|
139
|
-
element_array = [],
|
|
140
|
-
parse_context = CONTEXT_UNKNOWN,
|
|
253
|
+
/** @type {unknown} */ result = null,
|
|
254
|
+
/** @type {Record<string, unknown>|null|undefined} */ elements = undefined,
|
|
255
|
+
/** @type {unknown[]|null} */ element_array = [],
|
|
256
|
+
/** @type {0|1|3|4} */ parse_context = CONTEXT_UNKNOWN,
|
|
141
257
|
comment = 0,
|
|
142
258
|
fromHex = false,
|
|
143
259
|
decimal = false,
|
|
144
260
|
exponent = false,
|
|
145
261
|
exponent_sign = false,
|
|
146
262
|
exponent_digit = false,
|
|
147
|
-
gatheringStringFirstChar = null,
|
|
263
|
+
/** @type {number|null} */ gatheringStringFirstChar = null,
|
|
148
264
|
gatheringString = false,
|
|
149
265
|
gatheringNumber = false,
|
|
266
|
+
signSeen = false, // '+' or '-' seen; no number or keyword has followed it yet
|
|
150
267
|
stringEscape = false,
|
|
151
268
|
cr_escaped = false,
|
|
152
269
|
unicodeWide = false,
|
|
@@ -154,61 +271,94 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
154
271
|
stringHex = false,
|
|
155
272
|
hex_char = 0,
|
|
156
273
|
hex_char_len = 0,
|
|
157
|
-
completed = false
|
|
158
|
-
;
|
|
159
|
-
|
|
160
|
-
const context_stack = {
|
|
161
|
-
first
|
|
162
|
-
last
|
|
163
|
-
saved
|
|
164
|
-
push(node)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
274
|
+
completed = false,
|
|
275
|
+
dollarSeen = false;
|
|
276
|
+
|
|
277
|
+
const context_stack = /** @type {{
|
|
278
|
+
first: NodeInfo<Context>|null,
|
|
279
|
+
last: NodeInfo<Context>|null,
|
|
280
|
+
saved: NodeInfo<Context>|null|undefined,
|
|
281
|
+
push: (node: Context) => void,
|
|
282
|
+
pop: () => Context
|
|
283
|
+
}} */ ({
|
|
284
|
+
first : null,
|
|
285
|
+
last : null,
|
|
286
|
+
saved : null,
|
|
287
|
+
/**
|
|
288
|
+
* @param {Context} node
|
|
289
|
+
*/
|
|
290
|
+
push(node) {
|
|
291
|
+
/** @type {NodeInfo<Context>|null|undefined} */
|
|
292
|
+
let recover = this.saved;
|
|
293
|
+
if( recover ) { this.saved = recover.next; recover.node = node; recover.next = null; recover.prior = this.last; }
|
|
294
|
+
else { recover = /** @type {NodeInfo<Context>} */ ({ node : node, next : null, prior : this.last }); }
|
|
295
|
+
if( !this.last ) this.first = recover;
|
|
296
|
+
this.last = recover;
|
|
297
|
+
},
|
|
298
|
+
pop() {
|
|
299
|
+
const resultNode = this.last;
|
|
300
|
+
/* istanbul ignore next -- pop() is only ever called from parse_context states that were entered via a matching push(), so the stack can never be empty here */
|
|
301
|
+
if( !resultNode ) throw new Error( "context stack underflow" );
|
|
302
|
+
if( !(this.last = resultNode.prior ) ) this.first = null;
|
|
303
|
+
resultNode.next = this.saved; this.saved = resultNode;
|
|
304
|
+
return resultNode.node;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const inQueue = /** @type {{
|
|
309
|
+
save: NodeInfo<Json6Node>|null|undefined,
|
|
310
|
+
first: NodeInfo<Json6Node>|null|undefined,
|
|
311
|
+
last: NodeInfo<Json6Node>|null,
|
|
312
|
+
saved: NodeInfo<Json6Node>|null|undefined,
|
|
313
|
+
push: (node: Json6Node) => void,
|
|
314
|
+
shift: () => Json6Node|null,
|
|
315
|
+
unshift: (node: Json6Node) => void
|
|
316
|
+
}} */ ({
|
|
317
|
+
/** @type {NodeInfo<Json6Node>|null|undefined} */
|
|
318
|
+
save: undefined,
|
|
319
|
+
/** @type {NodeInfo<Json6Node>|null|undefined} */
|
|
320
|
+
first : null,
|
|
321
|
+
/** @type {NodeInfo<Json6Node>|null} */
|
|
322
|
+
last : null,
|
|
323
|
+
/** @type {NodeInfo<Json6Node>|null|undefined} */
|
|
324
|
+
saved : null,
|
|
325
|
+
/**
|
|
326
|
+
* @param {Json6Node} node
|
|
327
|
+
*/
|
|
328
|
+
push(node) {
|
|
329
|
+
/** @type {NodeInfo<Json6Node>|null|undefined} */
|
|
330
|
+
let recover = this.saved;
|
|
331
|
+
if( recover ) {this.saved = recover.next; recover.node = node; recover.next = null; recover.prior = this.last; }
|
|
332
|
+
else { recover = /** @type {NodeInfo<Json6Node>} */ ({ node : node, next : null, prior : this.last }); }
|
|
333
|
+
if( !this.last ) this.first = recover;
|
|
334
|
+
else this.last.next = recover;
|
|
335
|
+
this.last = recover;
|
|
336
|
+
},
|
|
337
|
+
shift() {
|
|
338
|
+
const resultNode = this.first;
|
|
339
|
+
if( !resultNode ) return null;
|
|
340
|
+
this.first = resultNode.next;
|
|
341
|
+
if( !this.first ) this.last = null;
|
|
342
|
+
resultNode.next = this.saved;
|
|
343
|
+
this.saved = resultNode;
|
|
344
|
+
// node is in saved...
|
|
345
|
+
return resultNode.node;
|
|
346
|
+
},
|
|
347
|
+
/**
|
|
348
|
+
* @param {Json6Node} node
|
|
349
|
+
*/
|
|
350
|
+
unshift(node) {
|
|
351
|
+
// usage in this module, recover will ALWAYS have a saved to use.
|
|
352
|
+
const recover = /** @type {NodeInfo<Json6Node>} */ (this.saved);
|
|
353
|
+
this.saved = recover.next; recover.node = node; recover.next = this.first; recover.prior = null;
|
|
354
|
+
if( !this.first ) this.last = recover;
|
|
355
|
+
this.first = recover;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
211
358
|
|
|
359
|
+
/**
|
|
360
|
+
* @param {string} leader
|
|
361
|
+
*/
|
|
212
362
|
function throwEndError( leader /* , c */ ) {
|
|
213
363
|
throw new Error( `${leader} at ${n} [${pos.line}:${pos.col}]`);
|
|
214
364
|
}
|
|
@@ -221,7 +371,8 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
221
371
|
case 1:
|
|
222
372
|
return throwEndError( "Comment began at end of document" );
|
|
223
373
|
case 2:
|
|
224
|
-
|
|
374
|
+
if( warnWithCommentWithoutEOL )
|
|
375
|
+
console.log( "Warning: '//' comment without end of line ended document" );
|
|
225
376
|
break;
|
|
226
377
|
case 3:
|
|
227
378
|
return throwEndError( "Open comment '/*' is missing close at end of document" );
|
|
@@ -237,14 +388,24 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
237
388
|
result = undefined;
|
|
238
389
|
return r;
|
|
239
390
|
},
|
|
240
|
-
|
|
391
|
+
/**
|
|
392
|
+
* @param {Json6ParseOptions} [resetOptions] Options for the next parse.
|
|
393
|
+
* When omitted, the options passed to `begin()` (if any) stay in
|
|
394
|
+
* effect.
|
|
395
|
+
*/
|
|
396
|
+
reset( resetOptions ) {
|
|
397
|
+
const activeOptions = resetOptions === undefined ? beginOptions : resetOptions;
|
|
398
|
+
esStrictCompatible = !!( activeOptions && activeOptions.esStrictCompatible );
|
|
399
|
+
forbidTemplateSubstitution = !!( activeOptions && activeOptions.forbidTemplateSubstitution );
|
|
400
|
+
warnWithCommentWithoutEOL = !!( activeOptions && activeOptions.warnWithCommentWithoutEOL );
|
|
401
|
+
normalizeNumericKeys = !!( activeOptions && activeOptions.normalizeNumericKeys );
|
|
241
402
|
word = WORD_POS_RESET;
|
|
242
403
|
status = true;
|
|
243
404
|
if( inQueue.last ) inQueue.last.next = inQueue.save;
|
|
244
405
|
inQueue.save = inQueue.first;
|
|
245
406
|
inQueue.first = inQueue.last = null;
|
|
246
|
-
if( context_stack.last ) context_stack.last.next = context_stack.
|
|
247
|
-
context_stack.
|
|
407
|
+
if( context_stack.last ) context_stack.last.next = context_stack.saved;
|
|
408
|
+
context_stack.saved = inQueue.first;
|
|
248
409
|
context_stack.first = context_stack.last = null;//= [];
|
|
249
410
|
element_array = null;
|
|
250
411
|
elements = undefined;
|
|
@@ -253,39 +414,61 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
253
414
|
val.name = null;
|
|
254
415
|
val.string = '';
|
|
255
416
|
pos.line = 1;
|
|
256
|
-
pos.col =
|
|
417
|
+
pos.col = 0;
|
|
257
418
|
negative = false;
|
|
419
|
+
signSeen = false;
|
|
258
420
|
comment = 0;
|
|
259
421
|
completed = false;
|
|
422
|
+
signSeen = false;
|
|
423
|
+
dollarSeen = false;
|
|
424
|
+
gatheringStringFirstChar = null;
|
|
260
425
|
gatheringString = false;
|
|
261
426
|
stringEscape = false; // string stringEscape intro
|
|
262
427
|
cr_escaped = false; // carraige return escaped
|
|
428
|
+
unicodeWide = false;
|
|
429
|
+
stringUnicode = false;
|
|
430
|
+
stringHex = false;
|
|
431
|
+
hex_char = 0;
|
|
432
|
+
hex_char_len = 0;
|
|
263
433
|
//stringUnicode = false; // reading \u
|
|
264
434
|
//unicodeWide = false; // reading \u{} in string
|
|
265
435
|
//stringHex = false; // reading \x in string
|
|
266
436
|
},
|
|
437
|
+
/**
|
|
438
|
+
* @param {unknown} [msg]
|
|
439
|
+
*/
|
|
267
440
|
write(msg) {
|
|
441
|
+
/** @type {number} */
|
|
268
442
|
let retcode;
|
|
443
|
+
// write() is the push interface; values only leave it through the callback.
|
|
444
|
+
// parse() drives the same parser through _write()/value() and needs no callback.
|
|
445
|
+
if( !cb ) throw new Error( "Callback function must be passed to begin, or you can't get values from the stream." );
|
|
269
446
|
if( msg !== undefined && typeof msg !== "string") msg = String(msg);
|
|
270
447
|
if( !status ) throw new Error( "Parser is in an error state, please reset." );
|
|
271
|
-
for( retcode = this._write(msg,false); retcode > 0; retcode = this._write() ) {
|
|
448
|
+
for( retcode = this._write(/** @type {string|undefined|null} */ (msg),false); retcode > 0; retcode = this._write() ) {
|
|
272
449
|
this.finalError();
|
|
273
|
-
if( typeof reviver === 'function' ) (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
450
|
+
if( typeof reviver === 'function' ) (/**
|
|
451
|
+
* @param {Record<string, unknown>} holder
|
|
452
|
+
* @param {string} key
|
|
453
|
+
* @returns {unknown}
|
|
454
|
+
*/
|
|
455
|
+
function walk(holder, key) {
|
|
456
|
+
const value = holder[key];
|
|
457
|
+
if (value && typeof value === 'object') {
|
|
458
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
459
|
+
for (const k in record) {
|
|
460
|
+
if (Object.prototype.hasOwnProperty.call(record, k)) {
|
|
461
|
+
const v = walk(record, k);
|
|
462
|
+
if (v !== undefined) {
|
|
463
|
+
record[k] = v;
|
|
464
|
+
} else {
|
|
465
|
+
delete record[k];
|
|
466
|
+
}
|
|
283
467
|
}
|
|
284
468
|
}
|
|
285
469
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}({'': result}, ''));
|
|
470
|
+
return reviver.call(holder, key, value);
|
|
471
|
+
}({'': result}, ''));
|
|
289
472
|
cb( result );
|
|
290
473
|
result = undefined;
|
|
291
474
|
|
|
@@ -295,110 +478,191 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
295
478
|
if( retcode )
|
|
296
479
|
this.finalError();
|
|
297
480
|
},
|
|
481
|
+
/**
|
|
482
|
+
* @param {string|undefined|null} [msg]
|
|
483
|
+
* @param {boolean} [complete_at_end]
|
|
484
|
+
*/
|
|
298
485
|
_write(msg,complete_at_end) {
|
|
486
|
+
/** @type {Json6Node|null} */
|
|
299
487
|
let input;
|
|
300
|
-
|
|
488
|
+
/** @type {string} */
|
|
489
|
+
let buf = "";
|
|
490
|
+
/** @type {number} */
|
|
301
491
|
let retval = 0;
|
|
302
492
|
|
|
493
|
+
/**
|
|
494
|
+
* @param {string} leader
|
|
495
|
+
* @param {number} c
|
|
496
|
+
*/
|
|
303
497
|
function throwError( leader, c ) {
|
|
304
498
|
throw new Error( `${leader} '${String.fromCodePoint( c )}' unexpected at ${n} (near '${buf.substr(n>4?(n-4):0,n>4?3:(n-1))}[${String.fromCodePoint( c )}]${buf.substr(n, 10)}') [${pos.line}:${pos.col}]`);
|
|
305
499
|
}
|
|
306
500
|
|
|
501
|
+
// Line bookkeeping for a character that has just been taken (n is past it).
|
|
502
|
+
// CR, LF, LS and PS each end a line; a CR directly followed by LF counts once,
|
|
503
|
+
// at the LF. (A CR that ends one write() chunk with the LF in the next counts
|
|
504
|
+
// twice; that is the one case this does not see.) Column resets to 0 so the
|
|
505
|
+
// next character lands on column 1.
|
|
506
|
+
/**
|
|
507
|
+
* @param {number} cInt
|
|
508
|
+
*/
|
|
509
|
+
function countLine( cInt ) {
|
|
510
|
+
if( cInt === 13/*'\r'*/ ) {
|
|
511
|
+
if( n < buf.length && buf.charCodeAt( n ) === 10 ) return;
|
|
512
|
+
}
|
|
513
|
+
else if( cInt !== 10/*'\n'*/ && cInt !== 0x2028 && cInt !== 0x2029 ) return;
|
|
514
|
+
pos.line++;
|
|
515
|
+
pos.col = 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
307
518
|
function RESET_VAL() {
|
|
308
519
|
val.value_type = VALUE_UNSET;
|
|
309
520
|
val.string = '';
|
|
521
|
+
signSeen = false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// A delimiter, or the end of the document, arrived while a value was still
|
|
525
|
+
// being spelled out: 'tru', 'nul', or a sign with nothing after it. Without
|
|
526
|
+
// this, '[tru]' was [] and 'tru' alone was undefined.
|
|
527
|
+
/**
|
|
528
|
+
* @param {number} [cInt] the delimiter; omitted at end of document
|
|
529
|
+
*/
|
|
530
|
+
function checkPartialValue( cInt ) {
|
|
531
|
+
if( word > WORD_POS_RESET && word < WORD_POS_FIELD ) {
|
|
532
|
+
if( cInt === undefined ) throwEndError( "Incomplete keyword" );
|
|
533
|
+
else throwError( "Incomplete keyword", cInt );
|
|
534
|
+
}
|
|
535
|
+
if( signSeen && val.value_type == VALUE_UNSET ) {
|
|
536
|
+
if( cInt === undefined ) throwEndError( "Sign with no number following" );
|
|
537
|
+
else throwError( "Sign with no number following", cInt );
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// A new value is starting; there must not already be a finished one waiting
|
|
542
|
+
// for a separator. Without this, '[1 2]' kept only the 2.
|
|
543
|
+
/**
|
|
544
|
+
* @param {number} cInt
|
|
545
|
+
*/
|
|
546
|
+
function checkValueSeparator( cInt ) {
|
|
547
|
+
if( val.value_type != VALUE_UNSET )
|
|
548
|
+
throwError( "Two values with no separator between them", cInt );
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// collectNumber only admits digits, a sign, one '.', one 'e' and a radix
|
|
552
|
+
// prefix, so a token Number() cannot read is malformed: '1e', '.5.', '.e3', '0x'.
|
|
553
|
+
// These used to come back as NaN.
|
|
554
|
+
function checkNumber() {
|
|
555
|
+
if( Number.isNaN( Number( val.string ) ) ) {
|
|
556
|
+
status = false;
|
|
557
|
+
throw new Error( `Invalid number '${val.string}' at ${n} [${pos.line}:${pos.col}]` );
|
|
558
|
+
}
|
|
310
559
|
}
|
|
311
560
|
|
|
312
561
|
function arrayPush() {
|
|
562
|
+
// invariant: only called while parse_context is CONTEXT_IN_ARRAY, which is only
|
|
563
|
+
// entered after '[' sets element_array to a real array.
|
|
564
|
+
const arr = /** @type {unknown[]} */ (element_array);
|
|
313
565
|
switch( val.value_type ){
|
|
314
566
|
case VALUE_NUMBER:
|
|
315
|
-
|
|
567
|
+
arr.push( (negative?-1:1) * Number( val.string ) );
|
|
316
568
|
break;
|
|
317
569
|
case VALUE_STRING:
|
|
318
|
-
|
|
570
|
+
arr.push( val.string );
|
|
319
571
|
break;
|
|
320
572
|
case VALUE_TRUE:
|
|
321
|
-
|
|
573
|
+
arr.push( true );
|
|
322
574
|
break;
|
|
323
575
|
case VALUE_FALSE:
|
|
324
|
-
|
|
576
|
+
arr.push( false );
|
|
325
577
|
break;
|
|
326
578
|
case VALUE_NEG_NAN:
|
|
327
|
-
|
|
579
|
+
arr.push( -NaN );
|
|
328
580
|
break;
|
|
329
581
|
case VALUE_NAN:
|
|
330
|
-
|
|
582
|
+
arr.push( NaN );
|
|
331
583
|
break;
|
|
332
584
|
case VALUE_NEG_INFINITY:
|
|
333
|
-
|
|
585
|
+
arr.push( -Infinity );
|
|
334
586
|
break;
|
|
335
587
|
case VALUE_INFINITY:
|
|
336
|
-
|
|
588
|
+
arr.push( Infinity );
|
|
337
589
|
break;
|
|
338
590
|
case VALUE_NULL:
|
|
339
|
-
|
|
591
|
+
arr.push( null );
|
|
340
592
|
break;
|
|
341
593
|
case VALUE_UNDEFINED:
|
|
342
|
-
|
|
594
|
+
arr.push( undefined );
|
|
343
595
|
break;
|
|
344
596
|
case VALUE_EMPTY:
|
|
345
|
-
|
|
346
|
-
delete
|
|
597
|
+
arr.push( undefined );
|
|
598
|
+
delete arr[arr.length-1];
|
|
347
599
|
break;
|
|
348
600
|
case VALUE_OBJECT:
|
|
349
|
-
|
|
601
|
+
arr.push( val.contains );
|
|
350
602
|
break;
|
|
351
603
|
case VALUE_ARRAY:
|
|
352
|
-
|
|
604
|
+
arr.push( val.contains );
|
|
353
605
|
break;
|
|
354
606
|
}
|
|
355
607
|
}
|
|
356
608
|
function objectPush() {
|
|
609
|
+
// invariant: only called while parse_context is CONTEXT_OBJECT_FIELD_VALUE, which is
|
|
610
|
+
// only entered after '{' sets elements to a real object.
|
|
611
|
+
const obj = /** @type {Record<string, unknown>} */ (elements);
|
|
612
|
+
const name = /** @type {string} */ (val.name);
|
|
357
613
|
switch( val.value_type ){
|
|
358
614
|
case VALUE_NUMBER:
|
|
359
|
-
|
|
615
|
+
obj[name] = ((negative?-1:1) * Number( val.string ));
|
|
360
616
|
break;
|
|
361
617
|
case VALUE_STRING:
|
|
362
|
-
|
|
618
|
+
obj[name] = ( val.string );
|
|
363
619
|
break;
|
|
364
620
|
case VALUE_TRUE:
|
|
365
|
-
|
|
621
|
+
obj[name] = ( true );
|
|
366
622
|
break;
|
|
367
623
|
case VALUE_FALSE:
|
|
368
|
-
|
|
624
|
+
obj[name] = ( false );
|
|
369
625
|
break;
|
|
370
626
|
case VALUE_NEG_NAN:
|
|
371
|
-
|
|
627
|
+
obj[name] = ( -NaN );
|
|
372
628
|
break;
|
|
373
629
|
case VALUE_NAN:
|
|
374
|
-
|
|
630
|
+
obj[name] = ( NaN );
|
|
375
631
|
break;
|
|
376
632
|
case VALUE_NEG_INFINITY:
|
|
377
|
-
|
|
633
|
+
obj[name] = ( -Infinity );
|
|
378
634
|
break;
|
|
379
635
|
case VALUE_INFINITY:
|
|
380
|
-
|
|
636
|
+
obj[name] = ( Infinity );
|
|
381
637
|
break;
|
|
382
638
|
case VALUE_NULL:
|
|
383
|
-
|
|
639
|
+
obj[name] = ( null );
|
|
384
640
|
break;
|
|
385
641
|
case VALUE_UNDEFINED:
|
|
386
|
-
|
|
642
|
+
obj[name] = ( undefined );
|
|
387
643
|
break;
|
|
388
644
|
case VALUE_OBJECT:
|
|
389
|
-
|
|
645
|
+
obj[name] = val.contains;
|
|
390
646
|
break;
|
|
391
647
|
case VALUE_ARRAY:
|
|
392
|
-
|
|
648
|
+
obj[name] = val.contains;
|
|
393
649
|
break;
|
|
394
650
|
}
|
|
395
651
|
}
|
|
396
652
|
|
|
653
|
+
/**
|
|
654
|
+
* @param {number} start_c
|
|
655
|
+
*/
|
|
397
656
|
function gatherString( start_c ) {
|
|
398
657
|
let retval = 0;
|
|
658
|
+
// Hoisted out of the per-character loop: these are constant for
|
|
659
|
+
// the whole string, so the default (non-strict) path adds no
|
|
660
|
+
// work beyond one predictable branch.
|
|
661
|
+
const strictQuote = esStrictCompatible && start_c !== 96/*'`'*/;
|
|
662
|
+
const trackTemplate = start_c === 96/*'`'*/ && forbidTemplateSubstitution;
|
|
399
663
|
while( retval == 0 && ( n < buf.length ) ) {
|
|
400
664
|
let str = buf.charAt(n);
|
|
401
|
-
const cInt = buf.codePointAt(n++);
|
|
665
|
+
const cInt = ensureDefined( buf.codePointAt(n++), "Unexpected end of string" );
|
|
402
666
|
if( cInt >= 0x10000 ) { str += buf.charAt(n); n++; }
|
|
403
667
|
//console.log( "gathering....", stringEscape, str, cInt, unicodeWide, stringHex, stringUnicode, hex_char_len );
|
|
404
668
|
pos.col++;
|
|
@@ -470,22 +734,15 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
470
734
|
}
|
|
471
735
|
switch( cInt ) {
|
|
472
736
|
case 13/*'\r'*/:
|
|
737
|
+
// \\ \r : a continuation; if \n follows it is counted there
|
|
473
738
|
cr_escaped = true;
|
|
474
|
-
|
|
739
|
+
countLine( cInt );
|
|
475
740
|
continue;
|
|
476
741
|
case 0x2028: // LS (Line separator)
|
|
477
742
|
case 0x2029: // PS (paragraph separator)
|
|
478
|
-
pos.col = 1; // no return to get newline reset, so reset line pos.
|
|
479
|
-
// Fallthrough
|
|
480
743
|
case 10/*'\n'*/:
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
cr_escaped = false;
|
|
484
|
-
} else {
|
|
485
|
-
// \\ \n
|
|
486
|
-
pos.col = 1;
|
|
487
|
-
}
|
|
488
|
-
pos.line++;
|
|
744
|
+
cr_escaped = false;
|
|
745
|
+
countLine( cInt );
|
|
489
746
|
break;
|
|
490
747
|
case 116/*'t'*/:
|
|
491
748
|
val.string += '\t';
|
|
@@ -494,6 +751,8 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
494
751
|
val.string += '\b';
|
|
495
752
|
break;
|
|
496
753
|
case 48/*'0'*/:
|
|
754
|
+
if( esStrictCompatible && buf.charCodeAt( n ) >= 48/*'0'*/ && buf.charCodeAt( n ) <= 57/*'9'*/ )
|
|
755
|
+
throwError( "Legacy octal string escapes are not allowed when the 'esStrictCompatible' option is set", buf.charCodeAt( n ) );
|
|
497
756
|
val.string += '\0';
|
|
498
757
|
break;
|
|
499
758
|
case 110/*'n'*/:
|
|
@@ -519,6 +778,8 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
519
778
|
hex_char = 0;
|
|
520
779
|
continue;
|
|
521
780
|
default:
|
|
781
|
+
if( esStrictCompatible && cInt >= 49/*'1'*/ && cInt <= 57/*'9'*/ )
|
|
782
|
+
throwError( "Legacy octal string escapes are not allowed when the 'esStrictCompatible' option is set", cInt );
|
|
522
783
|
val.string += str;
|
|
523
784
|
break;
|
|
524
785
|
}
|
|
@@ -527,14 +788,21 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
527
788
|
}
|
|
528
789
|
else if( cInt === 92/*'\\'*/ ) {
|
|
529
790
|
stringEscape = true;
|
|
791
|
+
dollarSeen = false;
|
|
530
792
|
}
|
|
531
793
|
else {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
794
|
+
// U+2028/U+2029 are legal, unescaped, inside string literals since
|
|
795
|
+
// ES2019 (the JSON-superset proposal); only \n and \r are illegal.
|
|
796
|
+
if( strictQuote && ( cInt === 10/*'\n'*/ || cInt === 13/*'\r'*/ ) )
|
|
797
|
+
throwError( "Literal line terminators inside single- or double-quoted strings require the 'esStrictCompatible' option to be off", cInt );
|
|
798
|
+
if( trackTemplate ) {
|
|
799
|
+
if( dollarSeen && cInt === 123/*'{'*/ )
|
|
800
|
+
throwError( "Template substitution ${ } is not allowed when the 'forbidTemplateSubstitution' option is set", cInt );
|
|
801
|
+
dollarSeen = cInt === 36/*'$'*/;
|
|
537
802
|
}
|
|
803
|
+
// \\ \r <any other character> : the CR was counted when seen
|
|
804
|
+
cr_escaped = false;
|
|
805
|
+
countLine( cInt );
|
|
538
806
|
val.string += str;
|
|
539
807
|
}
|
|
540
808
|
}
|
|
@@ -546,16 +814,18 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
546
814
|
let _n;
|
|
547
815
|
while( (_n = n) < buf.length ) {
|
|
548
816
|
const str = buf.charAt(_n);
|
|
549
|
-
const cInt = buf.codePointAt(n++);
|
|
817
|
+
const cInt = ensureDefined( buf.codePointAt(n++), "Unexpected end of number" );
|
|
550
818
|
if( cInt >= 0x10000 ) {
|
|
551
819
|
throwError( "fault while parsing number;", cInt );
|
|
552
820
|
}
|
|
553
821
|
//log('_DEBUG_PARSING', "in getting number:", n, cInt, String.fromCodePoint(cInt) );
|
|
822
|
+
pos.col++;
|
|
554
823
|
if( cInt == 95 /*_*/ )
|
|
555
824
|
continue;
|
|
556
|
-
pos.col++;
|
|
557
825
|
// leading zeros should be forbidden.
|
|
558
826
|
if( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) {
|
|
827
|
+
if( esStrictCompatible && val.string === '0' )
|
|
828
|
+
throwError( "Legacy octal / leading-zero numbers are not allowed when the 'esStrictCompatible' option is set", cInt );
|
|
559
829
|
if( exponent ) {
|
|
560
830
|
exponent_digit = true;
|
|
561
831
|
}
|
|
@@ -604,6 +874,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
604
874
|
}
|
|
605
875
|
} else {
|
|
606
876
|
if( cInt == 32/*' '*/ || cInt == 160/*   */ || cInt == 13 || cInt == 10 || cInt == 9
|
|
877
|
+
|| cInt == 11/*VT*/ || cInt == 12/*FF*/ || cInt == 0x2028/*LS*/ || cInt == 0x2029/*PS*/
|
|
607
878
|
|| cInt == 0xFEFF || cInt == 44/*','*/ || cInt == 125/*'}'*/ || cInt == 93/*']'*/
|
|
608
879
|
|| cInt == 58/*':'*/ ) {
|
|
609
880
|
break;
|
|
@@ -617,6 +888,9 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
617
888
|
}
|
|
618
889
|
}
|
|
619
890
|
}
|
|
891
|
+
// the character that ended the number was counted here and is handed back to
|
|
892
|
+
// the main loop, which counts it again.
|
|
893
|
+
if( n > _n ) pos.col--;
|
|
620
894
|
n = _n;
|
|
621
895
|
|
|
622
896
|
if( (!complete_at_end) && n == buf.length ) {
|
|
@@ -624,6 +898,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
624
898
|
}
|
|
625
899
|
else {
|
|
626
900
|
gatheringNumber = false;
|
|
901
|
+
checkNumber();
|
|
627
902
|
val.value_type = VALUE_NUMBER;
|
|
628
903
|
if( parse_context == CONTEXT_UNKNOWN ) {
|
|
629
904
|
completed = true;
|
|
@@ -641,6 +916,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
641
916
|
if( gatheringNumber ) {
|
|
642
917
|
//console.log( "Force completed.")
|
|
643
918
|
gatheringNumber = false;
|
|
919
|
+
checkNumber();
|
|
644
920
|
val.value_type = VALUE_NUMBER;
|
|
645
921
|
if( parse_context == CONTEXT_UNKNOWN ) {
|
|
646
922
|
completed = true;
|
|
@@ -653,8 +929,13 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
653
929
|
|
|
654
930
|
while( status && ( input = inQueue.shift() ) ) {
|
|
655
931
|
n = input.n;
|
|
656
|
-
buf =
|
|
932
|
+
// invariant: queued buffers are always given a string via `input.buf = msg` before being pushed.
|
|
933
|
+
buf = /** @type {string} */ (input.buf);
|
|
657
934
|
if( gatheringString ) {
|
|
935
|
+
/* istanbul ignore next -- gatheringString and gatheringStringFirstChar are always set/cleared together; this guards state that the public API can never actually produce */
|
|
936
|
+
if( gatheringStringFirstChar === null ) {
|
|
937
|
+
throw new Error( "Parser string start was unset" );
|
|
938
|
+
}
|
|
658
939
|
const string_status = gatherString( gatheringStringFirstChar );
|
|
659
940
|
if( string_status > 0 ) {
|
|
660
941
|
gatheringString = false;
|
|
@@ -667,12 +948,13 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
667
948
|
|
|
668
949
|
while( !completed && status && ( n < buf.length ) ) {
|
|
669
950
|
let str = buf.charAt(n);
|
|
670
|
-
const cInt = buf.codePointAt(n++);
|
|
951
|
+
const cInt = ensureDefined( buf.codePointAt(n++), "Unexpected end of input while parsing" );
|
|
671
952
|
if( cInt >= 0x10000 ) { str += buf.charAt(n); n++; }
|
|
672
953
|
//// log('_DEBUG_PARSING', "parsing at ", cInt, str );
|
|
673
954
|
//log('_DEBUG_LL', "processing: ", cInt, str, pos, comment, parse_context, word, val );
|
|
674
955
|
pos.col++;
|
|
675
956
|
if( comment ) { // '/'
|
|
957
|
+
countLine( cInt );
|
|
676
958
|
if( comment == 1 ) { // '/'
|
|
677
959
|
if( cInt == 42/*'*'*/ ) { comment = 3; } // '/*'
|
|
678
960
|
else if( cInt != 47/*'/'*/ ) { // '//'(NOT)
|
|
@@ -682,7 +964,8 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
682
964
|
}
|
|
683
965
|
else if( comment == 2 ) { // '// ...'
|
|
684
966
|
if( cInt == 10/*'\n'*/
|
|
685
|
-
|| cInt == 13/*'\r'*/
|
|
967
|
+
|| cInt == 13/*'\r'*/
|
|
968
|
+
|| cInt == 0x2028/*LS*/ || cInt == 0x2029/*PS*/ ) comment = 0;
|
|
686
969
|
}
|
|
687
970
|
else if( comment == 3 ){ // '/*... '
|
|
688
971
|
if( cInt == 42/*'*'*/ ) comment = 4;
|
|
@@ -702,6 +985,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
702
985
|
throwError( "fault while parsing; getting field name unexpected ", cInt );
|
|
703
986
|
// break;
|
|
704
987
|
}
|
|
988
|
+
checkValueSeparator( cInt );
|
|
705
989
|
{
|
|
706
990
|
const old_context = getContext();
|
|
707
991
|
//log('_DEBUG_PARSING', "Begin a new object; previously pushed into elements; but wait until trailing comma or close previously:%d", val.value_type );
|
|
@@ -734,13 +1018,15 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
734
1018
|
//log('_DEBUG_PARSING', "Begin a new array; previously pushed into elements; but wait until trailing comma or close previously:%d", val.value_type );
|
|
735
1019
|
|
|
736
1020
|
val.value_type = VALUE_ARRAY;
|
|
1021
|
+
/** @type {unknown[]} */
|
|
737
1022
|
const tmparr = [];
|
|
738
1023
|
if( parse_context == CONTEXT_UNKNOWN )
|
|
739
1024
|
result = element_array = tmparr;
|
|
740
1025
|
//else if( parse_context == CONTEXT_IN_ARRAY )
|
|
741
1026
|
// element_array.push( tmparr );
|
|
742
1027
|
else if( parse_context == CONTEXT_OBJECT_FIELD_VALUE )
|
|
743
|
-
elements
|
|
1028
|
+
// invariant: CONTEXT_OBJECT_FIELD_VALUE means elements/val.name were set by ':'.
|
|
1029
|
+
/** @type {Record<string, unknown>} */ (elements)[/** @type {string} */ (val.name)] = tmparr;
|
|
744
1030
|
|
|
745
1031
|
old_context.context = parse_context;
|
|
746
1032
|
old_context.elements = elements;
|
|
@@ -761,6 +1047,15 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
761
1047
|
////log('_DEBUG_PARSING', "colon context:", parse_context );
|
|
762
1048
|
if( parse_context == CONTEXT_OBJECT_FIELD ) {
|
|
763
1049
|
word = WORD_POS_RESET;
|
|
1050
|
+
if( esStrictCompatible && val.value_type !== VALUE_STRING
|
|
1051
|
+
&& !ES_IDENTIFIER_NAME.test( val.string )
|
|
1052
|
+
&& Number.isNaN( Number( val.string.replace( NUMERIC_SEPARATOR, '$1' ) ) ) )
|
|
1053
|
+
throwError( "Unquoted keys must be valid identifiers when the 'esStrictCompatible' option is set", cInt );
|
|
1054
|
+
if( normalizeNumericKeys && val.value_type !== VALUE_STRING && val.string !== '' ) {
|
|
1055
|
+
const numericKey = Number( val.string.replace( NUMERIC_SEPARATOR, '$1' ) );
|
|
1056
|
+
if( !Number.isNaN( numericKey ) )
|
|
1057
|
+
val.string = String( numericKey );
|
|
1058
|
+
}
|
|
764
1059
|
val.name = val.string;
|
|
765
1060
|
val.string = '';
|
|
766
1061
|
parse_context = CONTEXT_OBJECT_FIELD_VALUE;
|
|
@@ -779,8 +1074,13 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
779
1074
|
// allow starting a new word
|
|
780
1075
|
word = WORD_POS_RESET;
|
|
781
1076
|
}
|
|
1077
|
+
checkPartialValue( cInt );
|
|
782
1078
|
// coming back after pushing an array or sub-object will reset the context to FIELD, so an end with a field should still push value.
|
|
783
1079
|
if( ( parse_context == CONTEXT_OBJECT_FIELD ) ) {
|
|
1080
|
+
// a field name was collected but no ':' followed it: '{a}', '{a }', '{"a"}'.
|
|
1081
|
+
// This used to silently produce {}.
|
|
1082
|
+
if( word == WORD_POS_FIELD || word == WORD_POS_AFTER_FIELD || val.value_type == VALUE_STRING )
|
|
1083
|
+
throwError( "Object field name with no value", cInt );
|
|
784
1084
|
//log('_DEBUG_PARSING', "close object; empty object %d", val.value_type );
|
|
785
1085
|
//RESET_VAL();
|
|
786
1086
|
val.value_type = VALUE_OBJECT;
|
|
@@ -823,9 +1123,11 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
823
1123
|
throwError( "Fault while parsing; unexpected", cInt );
|
|
824
1124
|
}
|
|
825
1125
|
negative = false;
|
|
1126
|
+
signSeen = false;
|
|
826
1127
|
break;
|
|
827
1128
|
case 93/*']'*/:
|
|
828
1129
|
if( word == WORD_POS_END ) word = WORD_POS_RESET;
|
|
1130
|
+
checkPartialValue( cInt );
|
|
829
1131
|
if( parse_context == CONTEXT_IN_ARRAY ) {
|
|
830
1132
|
//log('_DEBUG_PARSING', "close array, push last element: %d", val.value_type );
|
|
831
1133
|
if( val.value_type != VALUE_UNSET ) {
|
|
@@ -850,9 +1152,11 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
850
1152
|
throwError( `bad context ${parse_context}; fault while parsing`, cInt );// fault
|
|
851
1153
|
}
|
|
852
1154
|
negative = false;
|
|
1155
|
+
signSeen = false;
|
|
853
1156
|
break;
|
|
854
1157
|
case 44/*','*/:
|
|
855
1158
|
if( word == WORD_POS_END ) word = WORD_POS_RESET; // allow collect new keyword
|
|
1159
|
+
checkPartialValue( cInt );
|
|
856
1160
|
//log('_DEBUG_PARSING', "comma context:", parse_context, val );
|
|
857
1161
|
if( parse_context == CONTEXT_IN_ARRAY ) {
|
|
858
1162
|
if( val.value_type == VALUE_UNSET )
|
|
@@ -878,12 +1182,16 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
878
1182
|
throwError( "bad context; excessive commas while parsing;", cInt );// fault
|
|
879
1183
|
}
|
|
880
1184
|
negative = false;
|
|
1185
|
+
signSeen = false;
|
|
881
1186
|
break;
|
|
882
1187
|
|
|
883
1188
|
default:
|
|
884
1189
|
if( parse_context == CONTEXT_OBJECT_FIELD ) {
|
|
885
1190
|
switch( cInt ) {
|
|
886
1191
|
case 96://'`':
|
|
1192
|
+
if( esStrictCompatible )
|
|
1193
|
+
throwError( "Backtick-quoted keys are not allowed when the 'esStrictCompatible' option is set", cInt );
|
|
1194
|
+
// falls through
|
|
887
1195
|
case 34://'"':
|
|
888
1196
|
case 39://'\'':
|
|
889
1197
|
if( word == WORD_POS_RESET ) {
|
|
@@ -903,14 +1211,16 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
903
1211
|
|
|
904
1212
|
break;
|
|
905
1213
|
case 10://'\n':
|
|
906
|
-
pos.line++;
|
|
907
|
-
pos.col = 1;
|
|
908
|
-
// fall through to normal space handling - just updated line/col position
|
|
909
1214
|
case 13://'\r':
|
|
910
1215
|
case 32://' ':
|
|
911
1216
|
case 160:// :
|
|
912
1217
|
case 9://'\t':
|
|
1218
|
+
case 11://VT
|
|
1219
|
+
case 12://FF
|
|
1220
|
+
case 0x2028://LS
|
|
1221
|
+
case 0x2029://PS
|
|
913
1222
|
case 0xFEFF: // ZWNBS is WS though
|
|
1223
|
+
countLine( cInt );
|
|
914
1224
|
if( word == WORD_POS_END ) { // allow collect new keyword
|
|
915
1225
|
word = WORD_POS_RESET;
|
|
916
1226
|
}
|
|
@@ -947,16 +1257,19 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
947
1257
|
break;
|
|
948
1258
|
}
|
|
949
1259
|
case 10://'\n':
|
|
950
|
-
pos.line++;
|
|
951
|
-
pos.col = 1;
|
|
952
|
-
// Fallthrough
|
|
953
1260
|
case 32://' ':
|
|
954
1261
|
case 160://  
|
|
955
1262
|
case 9://'\t':
|
|
956
1263
|
case 13://'\r':
|
|
1264
|
+
case 11://VT
|
|
1265
|
+
case 12://FF
|
|
1266
|
+
case 0x2028://LS
|
|
1267
|
+
case 0x2029://PS
|
|
957
1268
|
case 0xFEFF://'\uFEFF':
|
|
1269
|
+
countLine( cInt );
|
|
958
1270
|
if( word == WORD_POS_END ) {
|
|
959
1271
|
word = WORD_POS_RESET;
|
|
1272
|
+
signSeen = false;
|
|
960
1273
|
if( parse_context == CONTEXT_UNKNOWN ) {
|
|
961
1274
|
completed = true;
|
|
962
1275
|
}
|
|
@@ -970,7 +1283,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
970
1283
|
//----------------------------------------------------------
|
|
971
1284
|
// catch characters for true/false/null/undefined which are values outside of quotes
|
|
972
1285
|
case 116://'t':
|
|
973
|
-
if( word == WORD_POS_RESET ) word = WORD_POS_TRUE_1;
|
|
1286
|
+
if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_TRUE_1; }
|
|
974
1287
|
else if( word == WORD_POS_INFINITY_6 ) word = WORD_POS_INFINITY_7;
|
|
975
1288
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
976
1289
|
break;
|
|
@@ -981,7 +1294,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
981
1294
|
case 117://'u':
|
|
982
1295
|
if( word == WORD_POS_TRUE_2 ) word = WORD_POS_TRUE_3;
|
|
983
1296
|
else if( word == WORD_POS_NULL_1 ) word = WORD_POS_NULL_2;
|
|
984
|
-
else if( word == WORD_POS_RESET ) word = WORD_POS_UNDEFINED_1;
|
|
1297
|
+
else if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_UNDEFINED_1; }
|
|
985
1298
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
986
1299
|
break;
|
|
987
1300
|
case 101://'e':
|
|
@@ -996,7 +1309,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
996
1309
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
997
1310
|
break;
|
|
998
1311
|
case 110://'n':
|
|
999
|
-
if( word == WORD_POS_RESET ) word = WORD_POS_NULL_1;
|
|
1312
|
+
if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_NULL_1; }
|
|
1000
1313
|
else if( word == WORD_POS_UNDEFINED_1 ) word = WORD_POS_UNDEFINED_2;
|
|
1001
1314
|
else if( word == WORD_POS_UNDEFINED_6 ) word = WORD_POS_UNDEFINED_7;
|
|
1002
1315
|
else if( word == WORD_POS_INFINITY_1 ) word = WORD_POS_INFINITY_2;
|
|
@@ -1023,7 +1336,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
1023
1336
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1024
1337
|
break;
|
|
1025
1338
|
case 102://'f':
|
|
1026
|
-
if( word == WORD_POS_RESET ) word = WORD_POS_FALSE_1;
|
|
1339
|
+
if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_FALSE_1; }
|
|
1027
1340
|
else if( word == WORD_POS_UNDEFINED_4 ) word = WORD_POS_UNDEFINED_5;
|
|
1028
1341
|
else if( word == WORD_POS_INFINITY_2 ) word = WORD_POS_INFINITY_3;
|
|
1029
1342
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
@@ -1038,11 +1351,11 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
1038
1351
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1039
1352
|
break;
|
|
1040
1353
|
case 73://'I':
|
|
1041
|
-
if( word == WORD_POS_RESET ) word = WORD_POS_INFINITY_1;
|
|
1354
|
+
if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_INFINITY_1; }
|
|
1042
1355
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1043
1356
|
break;
|
|
1044
1357
|
case 78://'N':
|
|
1045
|
-
if( word == WORD_POS_RESET ) word = WORD_POS_NAN_1;
|
|
1358
|
+
if( word == WORD_POS_RESET ) { checkValueSeparator( cInt ); word = WORD_POS_NAN_1; }
|
|
1046
1359
|
else if( word == WORD_POS_NAN_2 ) { val.value_type = negative ? VALUE_NEG_NAN : VALUE_NAN; negative = false; word = WORD_POS_END; }
|
|
1047
1360
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1048
1361
|
break;
|
|
@@ -1051,21 +1364,35 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
1051
1364
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1052
1365
|
break;
|
|
1053
1366
|
case 45://'-':
|
|
1054
|
-
if( word == WORD_POS_RESET )
|
|
1367
|
+
if( word == WORD_POS_RESET ) {
|
|
1368
|
+
checkValueSeparator( cInt );
|
|
1369
|
+
if( esStrictCompatible && signSeen )
|
|
1370
|
+
throwError( "Multiple consecutive signs are not allowed when the 'esStrictCompatible' option is set", cInt );
|
|
1371
|
+
signSeen = true;
|
|
1372
|
+
negative = !negative;
|
|
1373
|
+
}
|
|
1055
1374
|
else { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1056
1375
|
break;
|
|
1057
1376
|
case 43://'+':
|
|
1058
1377
|
if( word !== WORD_POS_RESET ) { status = false; throwError( "fault parsing", cInt ); }// fault
|
|
1378
|
+
else {
|
|
1379
|
+
checkValueSeparator( cInt );
|
|
1380
|
+
if( esStrictCompatible && signSeen )
|
|
1381
|
+
throwError( "Multiple consecutive signs are not allowed when the 'esStrictCompatible' option is set", cInt );
|
|
1382
|
+
signSeen = true;
|
|
1383
|
+
}
|
|
1059
1384
|
break;
|
|
1060
1385
|
//
|
|
1061
1386
|
//----------------------------------------------------------
|
|
1062
1387
|
default:
|
|
1063
1388
|
if( ( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) || ( cInt == 43/*'+'*/ ) || ( cInt == 46/*'.'*/ ) || ( cInt == 45/*'-'*/ ) ) {
|
|
1389
|
+
checkValueSeparator( cInt );
|
|
1064
1390
|
fromHex = false;
|
|
1065
1391
|
exponent = false;
|
|
1066
1392
|
exponent_sign = false;
|
|
1067
1393
|
exponent_digit = false;
|
|
1068
1394
|
decimal = false;
|
|
1395
|
+
signSeen = false;
|
|
1069
1396
|
val.string = str;
|
|
1070
1397
|
input.n = n;
|
|
1071
1398
|
collectNumber();
|
|
@@ -1088,6 +1415,7 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
1088
1415
|
|
|
1089
1416
|
if( n == buf.length ) {
|
|
1090
1417
|
dropBuffer( input );
|
|
1418
|
+
if( complete_at_end ) checkPartialValue();
|
|
1091
1419
|
if( gatheringString || gatheringNumber || parse_context == CONTEXT_OBJECT_FIELD ) {
|
|
1092
1420
|
retval = 0;
|
|
1093
1421
|
}
|
|
@@ -1161,61 +1489,105 @@ JSON6.begin = function( cb, reviver ) {
|
|
|
1161
1489
|
|
|
1162
1490
|
const _parser = [Object.freeze( JSON6.begin() )];
|
|
1163
1491
|
let _parse_level = 0;
|
|
1164
|
-
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* @param {unknown} msg
|
|
1495
|
+
* @param {Json6Reviver} [reviver]
|
|
1496
|
+
* @param {Json6ParseOptions} [options]
|
|
1497
|
+
*/
|
|
1498
|
+
JSON6.parse = function( msg, reviver, options ) {
|
|
1165
1499
|
//var parser = JSON6.begin();
|
|
1166
1500
|
const parse_level = _parse_level++;
|
|
1167
1501
|
if( _parser.length <= parse_level )
|
|
1168
1502
|
_parser.push( Object.freeze( JSON6.begin() ) );
|
|
1169
1503
|
const parser = _parser[parse_level];
|
|
1170
1504
|
if (typeof msg !== "string") msg = String(msg);
|
|
1171
|
-
parser.reset();
|
|
1172
|
-
|
|
1505
|
+
parser.reset( options );
|
|
1506
|
+
try {
|
|
1507
|
+
if( parser._write( /** @type {string} */ (msg), true ) <= 0 ) {
|
|
1508
|
+
// nothing completed: empty document, whitespace, or only comments.
|
|
1509
|
+
parser.finalError();
|
|
1510
|
+
throw new Error( "No value found in document" );
|
|
1511
|
+
}
|
|
1173
1512
|
const result = parser.value();
|
|
1174
|
-
if (typeof reviver === 'function') (
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1513
|
+
if (typeof reviver === 'function') (/**
|
|
1514
|
+
* @param {Record<string, unknown>} holder
|
|
1515
|
+
* @param {string} key
|
|
1516
|
+
* @returns {unknown}
|
|
1517
|
+
*/
|
|
1518
|
+
function walk(holder, key) {
|
|
1519
|
+
const value = holder[key];
|
|
1520
|
+
if (value && typeof value === 'object') {
|
|
1521
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
1522
|
+
for (const k in record) {
|
|
1523
|
+
if (Object.prototype.hasOwnProperty.call(record, k)) {
|
|
1524
|
+
const v = walk(record, k);
|
|
1525
|
+
if (v !== undefined) {
|
|
1526
|
+
record[k] = v;
|
|
1527
|
+
} else {
|
|
1528
|
+
delete record[k];
|
|
1529
|
+
}
|
|
1184
1530
|
}
|
|
1185
1531
|
}
|
|
1186
1532
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}({'': result}, ''));
|
|
1190
|
-
_parse_level--;
|
|
1533
|
+
return reviver.call(holder, key, value);
|
|
1534
|
+
}({'': result}, ''));
|
|
1191
1535
|
return result;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1536
|
+
} finally {
|
|
1537
|
+
_parse_level--;
|
|
1538
|
+
}
|
|
1194
1539
|
};
|
|
1195
|
-
JSON6.stringify = JSON.stringify;
|
|
1196
1540
|
|
|
1197
1541
|
//---------------------------------------------------------------------------
|
|
1198
1542
|
// Stringify
|
|
1199
1543
|
//---------------------------------------------------------------------------
|
|
1200
1544
|
|
|
1545
|
+
/**
|
|
1546
|
+
* @typedef {((key: string, value: unknown) => unknown) | Array<string|number> | null} Json6Replacer
|
|
1547
|
+
*/
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* @typedef {object} Json6Stringifier
|
|
1551
|
+
* @property {(o?: unknown, r?: Json6Replacer, s?: string|number|null, as?: string) => string|undefined} stringify
|
|
1552
|
+
* @property {(q: string) => void} setQuote
|
|
1553
|
+
* @property {boolean} ignoreNonEnumerable
|
|
1554
|
+
* @property {boolean} sortKeys
|
|
1555
|
+
*/
|
|
1556
|
+
|
|
1557
|
+
/** @type {Json6Stringifier|null} */
|
|
1201
1558
|
JSON6.stringifierActive = null;
|
|
1202
1559
|
JSON6.stringifier = function() {
|
|
1203
1560
|
const keywords = { ["true"]:true,["false"]:false,["null"]:null,["NaN"]:NaN,["Infinity"]:Infinity,["undefined"]:undefined };
|
|
1204
1561
|
let useQuote = '"';
|
|
1205
1562
|
|
|
1206
1563
|
let ignoreNonEnumerable = false;
|
|
1564
|
+
// preserves the historical (sorted) output by default; set to `false` to
|
|
1565
|
+
// emit keys in their own enumeration order instead.
|
|
1566
|
+
let sortKeys = true;
|
|
1207
1567
|
|
|
1208
1568
|
return {
|
|
1569
|
+
/**
|
|
1570
|
+
* @param {unknown} [o]
|
|
1571
|
+
* @param {Json6Replacer} [r]
|
|
1572
|
+
* @param {string|number|null} [s]
|
|
1573
|
+
* @param {string} [as]
|
|
1574
|
+
* @returns {string|undefined}
|
|
1575
|
+
*/
|
|
1209
1576
|
stringify(o,r,s,as) { return stringify(this, o,r,s,as); },
|
|
1577
|
+
/**
|
|
1578
|
+
* @param {string} q
|
|
1579
|
+
*/
|
|
1210
1580
|
setQuote(q) { useQuote = q; },
|
|
1211
1581
|
get ignoreNonEnumerable() { return ignoreNonEnumerable; },
|
|
1212
1582
|
set ignoreNonEnumerable(val) { ignoreNonEnumerable = val; },
|
|
1583
|
+
get sortKeys() { return sortKeys; },
|
|
1584
|
+
set sortKeys(val) { sortKeys = val; },
|
|
1213
1585
|
};
|
|
1214
1586
|
|
|
1587
|
+
/**
|
|
1588
|
+
* @param {string} s
|
|
1589
|
+
*/
|
|
1215
1590
|
function getIdentifier(s) {
|
|
1216
|
-
if( "number" === typeof s && !isNaN( s ) ) {
|
|
1217
|
-
return ["'",s.toString(),"'"].join();
|
|
1218
|
-
}
|
|
1219
1591
|
if( !s.length ) return useQuote+useQuote;
|
|
1220
1592
|
// should check also for if any non ident in string...
|
|
1221
1593
|
return ( ( s in keywords /* [ "true","false","null","NaN","Infinity","undefined"].find( keyword=>keyword===s )*/
|
|
@@ -1223,16 +1595,21 @@ JSON6.stringifier = function() {
|
|
|
1223
1595
|
|| /((\n|\r|\t)|[ #{}()<>!+\-*/.:,])/.test( s ) )?(useQuote + JSON6.escape(s) +useQuote):s );
|
|
1224
1596
|
}
|
|
1225
1597
|
|
|
1598
|
+
/**
|
|
1599
|
+
* @param {Json6Stringifier} stringifier
|
|
1600
|
+
* @param {unknown} object
|
|
1601
|
+
* @param {Json6Replacer} [replacer]
|
|
1602
|
+
* @param {string|number|null} [space]
|
|
1603
|
+
* @param {string} [asField]
|
|
1604
|
+
* @returns {string|undefined}
|
|
1605
|
+
*/
|
|
1226
1606
|
function stringify( stringifier, object, replacer, space, asField ) {
|
|
1227
1607
|
if( object === undefined ) return "undefined";
|
|
1228
1608
|
if( object === null ) return "null";
|
|
1229
|
-
|
|
1230
|
-
let
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
const repType = typeof replacer;
|
|
1234
|
-
gap = "";
|
|
1235
|
-
indent = "";
|
|
1609
|
+
/** @type {string} */
|
|
1610
|
+
let gap = "";
|
|
1611
|
+
/** @type {string} */
|
|
1612
|
+
let indent = "";
|
|
1236
1613
|
const stringifier_ = JSON6.stringifierActive;
|
|
1237
1614
|
JSON6.stringifierActive = stringifier;
|
|
1238
1615
|
|
|
@@ -1243,21 +1620,21 @@ JSON6.stringifier = function() {
|
|
|
1243
1620
|
|
|
1244
1621
|
// If the space parameter is a number, make an indent string containing that
|
|
1245
1622
|
// many spaces.
|
|
1246
|
-
if (
|
|
1247
|
-
for (i = 0; i < space; i += 1) {
|
|
1623
|
+
if (typeof space === "number") {
|
|
1624
|
+
for (let i = 0; i < space; i += 1) {
|
|
1248
1625
|
indent += " ";
|
|
1249
1626
|
}
|
|
1250
1627
|
|
|
1251
1628
|
// If the space parameter is a string, it will be used as the indent string.
|
|
1252
|
-
} else if (
|
|
1629
|
+
} else if (typeof space === "string") {
|
|
1253
1630
|
indent = space;
|
|
1254
1631
|
}
|
|
1255
1632
|
|
|
1256
1633
|
// If there is a replacer, it must be a function or an array.
|
|
1257
1634
|
// Otherwise, throw an error.
|
|
1258
1635
|
const rep = replacer;
|
|
1259
|
-
if( replacer &&
|
|
1260
|
-
&& (
|
|
1636
|
+
if( replacer && typeof replacer !== "function"
|
|
1637
|
+
&& ( typeof replacer !== "object"
|
|
1261
1638
|
|| typeof replacer.length !== "number"
|
|
1262
1639
|
)) {
|
|
1263
1640
|
throw new Error("JSON6.stringify unknown replacer type.");
|
|
@@ -1270,16 +1647,26 @@ JSON6.stringifier = function() {
|
|
|
1270
1647
|
|
|
1271
1648
|
|
|
1272
1649
|
// from https://github.com/douglascrockford/JSON-js/blob/master/json2.js#L181
|
|
1650
|
+
/**
|
|
1651
|
+
* @param {string} key
|
|
1652
|
+
* @param {Record<string, unknown>} holder
|
|
1653
|
+
* @returns {string|undefined}
|
|
1654
|
+
*/
|
|
1273
1655
|
function str(key, holder) {
|
|
1274
1656
|
// Produce a string from holder[key].
|
|
1275
|
-
let i; // The loop counter.
|
|
1276
|
-
let k; // The member key.
|
|
1277
|
-
let v; // The member value.
|
|
1278
|
-
let length;
|
|
1279
1657
|
const mind = gap;
|
|
1280
|
-
|
|
1658
|
+
/** @type {unknown} */
|
|
1281
1659
|
let value = holder[key];
|
|
1282
|
-
|
|
1660
|
+
// A string *value* must always be quoted: unlike an object key,
|
|
1661
|
+
// a bareword here would parse back as an identifier/variable
|
|
1662
|
+
// reference rather than as the string itself, so `getIdentifier`
|
|
1663
|
+
// (which intentionally leaves identifier-shaped keys unquoted)
|
|
1664
|
+
// is not applicable to values. Using it here previously emitted
|
|
1665
|
+
// invalid, non-reparseable output for any identifier-shaped
|
|
1666
|
+
// string value (e.g. `"hello"` became the bare word `hello`).
|
|
1667
|
+
if( "string" === typeof value ) {
|
|
1668
|
+
value = useQuote + JSON6.escape( value ) + useQuote;
|
|
1669
|
+
}
|
|
1283
1670
|
|
|
1284
1671
|
if( value !== undefined
|
|
1285
1672
|
&& value !== null
|
|
@@ -1305,7 +1692,7 @@ JSON6.stringifier = function() {
|
|
|
1305
1692
|
return '' + value;//useQuote+JSOX.escape( value )+useQuote;
|
|
1306
1693
|
case "boolean":
|
|
1307
1694
|
return String(value);
|
|
1308
|
-
case "object":
|
|
1695
|
+
case "object": {
|
|
1309
1696
|
|
|
1310
1697
|
//_DEBUG_STRINGIFY && console.log( "ENTERING OBJECT EMISSION WITH:", v );
|
|
1311
1698
|
//if( v ) return v;
|
|
@@ -1316,19 +1703,50 @@ JSON6.stringifier = function() {
|
|
|
1316
1703
|
return "null";
|
|
1317
1704
|
}
|
|
1318
1705
|
|
|
1706
|
+
if( Array.isArray( value ) ) {
|
|
1707
|
+
// Arrays used to fall through to the object path below and come out
|
|
1708
|
+
// as {"0":..,"1":..}. Holes stay holes ( [1,,3] ) and undefined stays
|
|
1709
|
+
// undefined; both are JSON6 syntax and both parse back to what they were.
|
|
1710
|
+
const arr = /** @type {unknown[]} */ (value);
|
|
1711
|
+
const holder_ = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (arr));
|
|
1712
|
+
gap += indent;
|
|
1713
|
+
/** @type {string[]} */
|
|
1714
|
+
const items = [];
|
|
1715
|
+
for( let i = 0; i < arr.length; i++ ) {
|
|
1716
|
+
if( !( i in arr ) ) {
|
|
1717
|
+
items.push( "" );
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
const v = str( String( i ), holder_ );
|
|
1721
|
+
items.push( v === undefined ? "undefined" : v );
|
|
1722
|
+
}
|
|
1723
|
+
// a trailing hole needs its own comma, since "[1,]" parses as [1].
|
|
1724
|
+
const trailingHole = arr.length > 0 && !( ( arr.length - 1 ) in arr );
|
|
1725
|
+
const v = items.length === 0
|
|
1726
|
+
? "[]"
|
|
1727
|
+
: gap
|
|
1728
|
+
? "[\n" + gap + items.join( ",\n" + gap ) + ( trailingHole ? "," : "" ) + "\n" + mind + "]"
|
|
1729
|
+
: "[" + items.join( "," ) + ( trailingHole ? "," : "" ) + "]";
|
|
1730
|
+
gap = mind;
|
|
1731
|
+
return v;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
1735
|
+
|
|
1319
1736
|
// Make an array to hold the partial results of stringifying this object value.
|
|
1320
1737
|
|
|
1321
1738
|
gap += indent;
|
|
1322
|
-
|
|
1739
|
+
/** @type {string[]} */
|
|
1740
|
+
const partial = [];
|
|
1323
1741
|
|
|
1324
1742
|
// If the replacer is an array, use it to select the members to be stringified.
|
|
1325
1743
|
if (rep && typeof rep === "object") {
|
|
1326
|
-
length = rep.length;
|
|
1744
|
+
const length = rep.length;
|
|
1327
1745
|
//_DEBUG_STRINGIFY && console.log( "Working through replacer" );
|
|
1328
|
-
for (i = 0; i < length; i += 1) {
|
|
1746
|
+
for (let i = 0; i < length; i += 1) {
|
|
1329
1747
|
if (typeof rep[i] === "string") {
|
|
1330
|
-
k = rep[i];
|
|
1331
|
-
v = str(k,
|
|
1748
|
+
const k = /** @type {string} */ (rep[i]);
|
|
1749
|
+
const v = str(k, record);
|
|
1332
1750
|
if (v) {
|
|
1333
1751
|
partial.push(getIdentifier(k) + (
|
|
1334
1752
|
(gap)
|
|
@@ -1341,32 +1759,37 @@ JSON6.stringifier = function() {
|
|
|
1341
1759
|
} else {
|
|
1342
1760
|
|
|
1343
1761
|
// Otherwise, iterate through all of the keys in the object.
|
|
1762
|
+
/** @type {string[]} */
|
|
1344
1763
|
const keys = [];
|
|
1345
1764
|
//_DEBUG_STRINGIFY && console.log( "is something in something?", k, value );
|
|
1346
|
-
for (k in
|
|
1765
|
+
for (const k in record) {
|
|
1347
1766
|
if( ignoreNonEnumerable )
|
|
1348
|
-
if( !Object.prototype.propertyIsEnumerable.call(
|
|
1767
|
+
if( !Object.prototype.propertyIsEnumerable.call( record, k ) ){
|
|
1349
1768
|
//_DEBUG_STRINGIFY && console.log( "skipping non-enuerable?", k );
|
|
1350
1769
|
continue;
|
|
1351
1770
|
}
|
|
1352
1771
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
keys
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1772
|
+
if (Object.prototype.hasOwnProperty.call(record, k)) {
|
|
1773
|
+
if( sortKeys ) {
|
|
1774
|
+
// sort properties into keys.
|
|
1775
|
+
let n;
|
|
1776
|
+
for( n = 0; n < keys.length; n++ )
|
|
1777
|
+
if( keys[n] > k ) {
|
|
1778
|
+
keys.splice(n,0,k );
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
if( n === keys.length )
|
|
1782
|
+
keys.push(k);
|
|
1783
|
+
} else {
|
|
1362
1784
|
keys.push(k);
|
|
1785
|
+
}
|
|
1363
1786
|
}
|
|
1364
1787
|
}
|
|
1365
1788
|
//_DEBUG_STRINGIFY && console.log( "Expanding object keys:", v, keys );
|
|
1366
1789
|
for(let n = 0; n < keys.length; n++) {
|
|
1367
|
-
k = keys[n];
|
|
1368
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
1369
|
-
v = str(k,
|
|
1790
|
+
const k = keys[n];
|
|
1791
|
+
if (Object.prototype.hasOwnProperty.call(record, k)) {
|
|
1792
|
+
const v = str(k, record);
|
|
1370
1793
|
if (v) {
|
|
1371
1794
|
partial.push(getIdentifier(k)+ (
|
|
1372
1795
|
(gap)
|
|
@@ -1381,7 +1804,7 @@ JSON6.stringifier = function() {
|
|
|
1381
1804
|
// Join all of the member texts together, separated with commas,
|
|
1382
1805
|
// and wrap them in braces.
|
|
1383
1806
|
//_DEBUG_STRINGIFY && console.log( "partial:", partial, protoConverter )
|
|
1384
|
-
v = '' +
|
|
1807
|
+
const v = '' +
|
|
1385
1808
|
( partial.length === 0
|
|
1386
1809
|
? "{}"
|
|
1387
1810
|
: gap
|
|
@@ -1392,13 +1815,33 @@ JSON6.stringifier = function() {
|
|
|
1392
1815
|
//_DEBUG_STRINGIFY && console.log(" Resulting phrase from this part is:", v );
|
|
1393
1816
|
return v;
|
|
1394
1817
|
}
|
|
1818
|
+
}
|
|
1395
1819
|
}
|
|
1396
1820
|
}
|
|
1397
1821
|
};
|
|
1398
1822
|
|
|
1399
|
-
|
|
1823
|
+
/**
|
|
1824
|
+
* @typedef {object} Json6StringifyOptions
|
|
1825
|
+
* @property {boolean} [sortKeys] When `false`, object keys are emitted in their
|
|
1826
|
+
* own enumeration order instead of sorted. Defaults to `true`.
|
|
1827
|
+
*/
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* @param {unknown} object
|
|
1831
|
+
* @param {Json6Replacer} [replacer]
|
|
1832
|
+
* @param {string|number} [space]
|
|
1833
|
+
* @param {Json6StringifyOptions} [options]
|
|
1834
|
+
* @returns {string|undefined}
|
|
1835
|
+
*/
|
|
1836
|
+
JSON6.stringify = function( object, replacer, space, options ) {
|
|
1400
1837
|
const stringifier = JSON6.stringifier();
|
|
1838
|
+
if( options && options.sortKeys === false ) stringifier.sortKeys = false;
|
|
1401
1839
|
return stringifier.stringify( object, replacer, space );
|
|
1402
1840
|
};
|
|
1403
1841
|
|
|
1404
1842
|
JSON6.version = version;
|
|
1843
|
+
|
|
1844
|
+
// CommonJS export; guarded so lib/json6.js can also be loaded directly with a
|
|
1845
|
+
// <script> tag, where JSON6 is simply left as a global.
|
|
1846
|
+
/* istanbul ignore next -- the non-CommonJS branch is only reachable from a browser <script> tag */
|
|
1847
|
+
if( typeof module === "object" && module && module.exports ) module.exports = JSON6;
|