jsonh-ts 1.0.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/build/index.d.ts +22 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +22 -0
- package/build/index.js.map +1 -0
- package/build/json-token-type.d.ts +77 -0
- package/build/json-token-type.d.ts.map +1 -0
- package/build/json-token-type.js +79 -0
- package/build/json-token-type.js.map +1 -0
- package/build/jsonh-number-parser.d.ts +21 -0
- package/build/jsonh-number-parser.d.ts.map +1 -0
- package/build/jsonh-number-parser.js +174 -0
- package/build/jsonh-number-parser.js.map +1 -0
- package/build/jsonh-reader-options.d.ts +25 -0
- package/build/jsonh-reader-options.d.ts.map +1 -0
- package/build/jsonh-reader-options.js +26 -0
- package/build/jsonh-reader-options.js.map +1 -0
- package/build/jsonh-reader.d.ts +68 -0
- package/build/jsonh-reader.d.ts.map +1 -0
- package/build/jsonh-reader.js +1218 -0
- package/build/jsonh-reader.js.map +1 -0
- package/build/jsonh-token.d.ts +21 -0
- package/build/jsonh-token.d.ts.map +1 -0
- package/build/jsonh-token.js +36 -0
- package/build/jsonh-token.js.map +1 -0
- package/build/jsonh-version.d.ts +15 -0
- package/build/jsonh-version.d.ts.map +1 -0
- package/build/jsonh-version.js +17 -0
- package/build/jsonh-version.js.map +1 -0
- package/build/result-helpers.d.ts +6 -0
- package/build/result-helpers.d.ts.map +1 -0
- package/build/result-helpers.js +11 -0
- package/build/result-helpers.js.map +1 -0
- package/build/result.d.ts +48 -0
- package/build/result.d.ts.map +1 -0
- package/build/result.js +95 -0
- package/build/result.js.map +1 -0
- package/build/string-text-reader.d.ts +11 -0
- package/build/string-text-reader.d.ts.map +1 -0
- package/build/string-text-reader.js +33 -0
- package/build/string-text-reader.js.map +1 -0
- package/build/text-reader.d.ts +20 -0
- package/build/text-reader.d.ts.map +1 -0
- package/build/text-reader.js +19 -0
- package/build/text-reader.js.map +1 -0
- package/index.ts +21 -0
- package/json-token-type.ts +77 -0
- package/jsonh-number-parser.ts +191 -0
- package/jsonh-reader-options.ts +26 -0
- package/jsonh-reader.ts +1317 -0
- package/jsonh-token.ts +37 -0
- package/jsonh-version.ts +15 -0
- package/package.json +30 -0
- package/result.ts +97 -0
- package/string-text-reader.ts +35 -0
- package/text-reader.ts +30 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
const JsonhReaderOptions = require("./jsonh-reader-options.js");
|
|
4
|
+
const TextReader = require("./text-reader.js");
|
|
5
|
+
const StringTextReader = require("./string-text-reader.js");
|
|
6
|
+
const JsonhToken = require("./jsonh-token.js");
|
|
7
|
+
const JsonTokenType = require("./json-token-type.js");
|
|
8
|
+
const JsonhNumberParser = require("./jsonh-number-parser.js");
|
|
9
|
+
const Result = require("./result.js");
|
|
10
|
+
/**
|
|
11
|
+
* A reader that reads JSONH tokens from a string.
|
|
12
|
+
*/
|
|
13
|
+
class JsonhReader {
|
|
14
|
+
/**
|
|
15
|
+
* The text reader to read characters from.
|
|
16
|
+
*/
|
|
17
|
+
#textReader;
|
|
18
|
+
/**
|
|
19
|
+
* The text reader to read characters from.
|
|
20
|
+
*/
|
|
21
|
+
get textReader() {
|
|
22
|
+
return this.#textReader;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The options to use when reading JSONH.
|
|
26
|
+
*/
|
|
27
|
+
#options;
|
|
28
|
+
/**
|
|
29
|
+
* The options to use when reading JSONH.
|
|
30
|
+
*/
|
|
31
|
+
get options() {
|
|
32
|
+
return this.#options;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The number of characters read from {@link string}.
|
|
36
|
+
*/
|
|
37
|
+
#charCounter;
|
|
38
|
+
/**
|
|
39
|
+
* The number of characters read from {@link string}.
|
|
40
|
+
*/
|
|
41
|
+
get charCounter() {
|
|
42
|
+
return this.#charCounter;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Characters that cannot be used unescaped in quoteless strings.
|
|
46
|
+
*/
|
|
47
|
+
static #reservedChars = ['\\', ',', ':', '[', ']', '{', '}', '/', '#', '"', '\''];
|
|
48
|
+
/**
|
|
49
|
+
* Characters that are considered newlines.
|
|
50
|
+
*/
|
|
51
|
+
static #newlineChars = ['\n', '\r', '\u2028', '\u2029'];
|
|
52
|
+
/**
|
|
53
|
+
* Characters that are considered whitespace.
|
|
54
|
+
*/
|
|
55
|
+
static #whitespaceChars = [
|
|
56
|
+
'\u0020', '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005',
|
|
57
|
+
'\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000', '\u2028',
|
|
58
|
+
'\u2029', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u0085',
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Constructs a reader that reads JSONH from a text reader.
|
|
62
|
+
*/
|
|
63
|
+
constructor(textReader, options = new JsonhReaderOptions()) {
|
|
64
|
+
if (typeof textReader === "string") {
|
|
65
|
+
throw new Error("Do not pass a string to new JsonhReader(). Use JsonhReader.fromString().");
|
|
66
|
+
}
|
|
67
|
+
this.#textReader = textReader;
|
|
68
|
+
this.#options = options;
|
|
69
|
+
this.#charCounter = 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Constructs a reader that reads JSONH from a text reader.
|
|
73
|
+
*/
|
|
74
|
+
static fromTextReader(textReader, options = new JsonhReaderOptions()) {
|
|
75
|
+
return new _a(textReader, options);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Constructs a reader that reads JSONH from a string.
|
|
79
|
+
*/
|
|
80
|
+
static fromString(string, options = new JsonhReaderOptions()) {
|
|
81
|
+
return new _a(new StringTextReader(string), options);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parses a single element from a text reader.
|
|
85
|
+
*/
|
|
86
|
+
static parseElementfromTextReader(textReader) {
|
|
87
|
+
return new _a(textReader).parseElement();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parses a single element from a string.
|
|
91
|
+
*/
|
|
92
|
+
static parseElementFromString(string) {
|
|
93
|
+
return this.fromString(string).parseElement();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parses a single element from the reader.
|
|
97
|
+
*/
|
|
98
|
+
parseElement() {
|
|
99
|
+
let currentNodes = [];
|
|
100
|
+
let currentPropertyName = null;
|
|
101
|
+
let submitNode = function (node) {
|
|
102
|
+
// Root value
|
|
103
|
+
if (currentNodes.length === 0) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// Array item
|
|
107
|
+
if (currentPropertyName === null) {
|
|
108
|
+
currentNodes.at(-1).push(node);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// Object property
|
|
112
|
+
else {
|
|
113
|
+
currentNodes.at(-1)[currentPropertyName] = node;
|
|
114
|
+
currentPropertyName = null;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
let startNode = function (node) {
|
|
119
|
+
submitNode(node);
|
|
120
|
+
currentNodes.push(node);
|
|
121
|
+
};
|
|
122
|
+
for (let tokenResult of this.readElement()) {
|
|
123
|
+
// Check error
|
|
124
|
+
if (tokenResult.isError) {
|
|
125
|
+
return Result.fromError(tokenResult.error);
|
|
126
|
+
}
|
|
127
|
+
switch (tokenResult.value.jsonType) {
|
|
128
|
+
// Null
|
|
129
|
+
case JsonTokenType.Null: {
|
|
130
|
+
let node = null;
|
|
131
|
+
if (submitNode(node)) {
|
|
132
|
+
return Result.fromValue(node);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
// True
|
|
137
|
+
case JsonTokenType.True: {
|
|
138
|
+
let node = true;
|
|
139
|
+
if (submitNode(node)) {
|
|
140
|
+
return Result.fromValue(node);
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
// False
|
|
145
|
+
case JsonTokenType.False: {
|
|
146
|
+
let node = false;
|
|
147
|
+
if (submitNode(node)) {
|
|
148
|
+
return Result.fromValue(node);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
// String
|
|
153
|
+
case JsonTokenType.String: {
|
|
154
|
+
let node = tokenResult.value.value;
|
|
155
|
+
if (submitNode(node)) {
|
|
156
|
+
return Result.fromValue(node);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
// Number
|
|
161
|
+
case JsonTokenType.Number: {
|
|
162
|
+
// TODO
|
|
163
|
+
let result = JsonhNumberParser.parse(tokenResult.value.value);
|
|
164
|
+
if (result.isError) {
|
|
165
|
+
return Result.fromError(result.error);
|
|
166
|
+
}
|
|
167
|
+
let node = result.value;
|
|
168
|
+
if (submitNode(node)) {
|
|
169
|
+
return Result.fromValue(node);
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
// Start Object
|
|
174
|
+
case JsonTokenType.StartObject: {
|
|
175
|
+
let node = {};
|
|
176
|
+
startNode(node);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
// Start Array
|
|
180
|
+
case JsonTokenType.StartArray: {
|
|
181
|
+
let node = [];
|
|
182
|
+
startNode(node);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
// End Object/Array
|
|
186
|
+
case JsonTokenType.EndObject:
|
|
187
|
+
case JsonTokenType.EndArray: {
|
|
188
|
+
// Nested node
|
|
189
|
+
if (currentNodes.length > 1) {
|
|
190
|
+
currentNodes.pop();
|
|
191
|
+
}
|
|
192
|
+
// Root node
|
|
193
|
+
else {
|
|
194
|
+
return Result.fromValue(currentNodes.at(-1));
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
// Property Name
|
|
199
|
+
case JsonTokenType.PropertyName: {
|
|
200
|
+
currentPropertyName = tokenResult.value.value;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
// Comment
|
|
204
|
+
case JsonTokenType.Comment: {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
// Not Implemented
|
|
208
|
+
default: {
|
|
209
|
+
return Result.fromError(new Error("Token type not implemented"));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// End of input
|
|
214
|
+
return Result.fromError(new Error("Expected token, got end of input"));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Tries to find the given property name in the reader.
|
|
218
|
+
* For example, to find `c`:
|
|
219
|
+
* ```
|
|
220
|
+
* // Original position
|
|
221
|
+
* {
|
|
222
|
+
* "a": "1",
|
|
223
|
+
* "b": {
|
|
224
|
+
* "c": "2"
|
|
225
|
+
* },
|
|
226
|
+
* "c": // Final position
|
|
227
|
+
* "3"
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
findPropertyValue(propertyName) {
|
|
232
|
+
let currentDepth = 0;
|
|
233
|
+
for (let tokenResult of this.readElement()) {
|
|
234
|
+
// Check error
|
|
235
|
+
if (tokenResult.isError) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
switch (tokenResult.value.jsonType) {
|
|
239
|
+
// Start structure
|
|
240
|
+
case JsonTokenType.StartObject:
|
|
241
|
+
case JsonTokenType.StartArray: {
|
|
242
|
+
currentDepth++;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
// End structure
|
|
246
|
+
case JsonTokenType.EndObject:
|
|
247
|
+
case JsonTokenType.EndArray: {
|
|
248
|
+
currentDepth--;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
// Property name
|
|
252
|
+
case JsonTokenType.PropertyName: {
|
|
253
|
+
if (currentDepth === 1 && tokenResult.value.value === propertyName) {
|
|
254
|
+
// Path found
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Path not found
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Reads a single element from the reader.
|
|
266
|
+
*/
|
|
267
|
+
*readElement() {
|
|
268
|
+
// Comments & whitespace
|
|
269
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
270
|
+
if (token.isError) {
|
|
271
|
+
yield token;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
yield token;
|
|
275
|
+
}
|
|
276
|
+
// Peek result
|
|
277
|
+
let next = this.#peek();
|
|
278
|
+
if (next === null) {
|
|
279
|
+
yield Result.fromError(new Error("Expected token, got end of input"));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Object
|
|
283
|
+
if (next === '{') {
|
|
284
|
+
for (let token of this.#readObject()) {
|
|
285
|
+
if (token.isError) {
|
|
286
|
+
yield token;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
yield token;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Array
|
|
293
|
+
else if (next === '[') {
|
|
294
|
+
for (let token of this.#readArray()) {
|
|
295
|
+
if (token.isError) {
|
|
296
|
+
yield token;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
yield token;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Primitive value (null, true, false, string, number)
|
|
303
|
+
else {
|
|
304
|
+
let token = this.#readPrimitiveElement();
|
|
305
|
+
if (token.isError) {
|
|
306
|
+
yield token;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Detect braceless object from property name
|
|
310
|
+
if (token.value.jsonType === JsonTokenType.String) {
|
|
311
|
+
// Try read property name
|
|
312
|
+
let propertyNameTokens = [];
|
|
313
|
+
for (let propertyNameToken of this.#readPropertyName(token.value.value)) {
|
|
314
|
+
// Possible braceless object
|
|
315
|
+
if (!propertyNameToken.isError) {
|
|
316
|
+
propertyNameTokens.push(propertyNameToken.value);
|
|
317
|
+
}
|
|
318
|
+
// Primitive value (error reading property name)
|
|
319
|
+
else {
|
|
320
|
+
yield token;
|
|
321
|
+
for (let nonPropertyNameToken of propertyNameTokens) {
|
|
322
|
+
yield Result.fromValue(nonPropertyNameToken);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Braceless object
|
|
328
|
+
for (let objectToken of this.#readBracelessObject(propertyNameTokens)) {
|
|
329
|
+
if (objectToken.isError) {
|
|
330
|
+
yield objectToken;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
yield objectToken;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Primitive value
|
|
337
|
+
else {
|
|
338
|
+
yield token;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
*#readObject() {
|
|
343
|
+
// Opening brace
|
|
344
|
+
if (!this.#readOne('{')) {
|
|
345
|
+
// Braceless object
|
|
346
|
+
for (let token of this.#readBracelessObject()) {
|
|
347
|
+
if (token.isError) {
|
|
348
|
+
yield token;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
yield token;
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// Start object
|
|
356
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.StartObject));
|
|
357
|
+
while (true) {
|
|
358
|
+
// Comments & whitespace
|
|
359
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
360
|
+
if (token.isError) {
|
|
361
|
+
yield token;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
yield token;
|
|
365
|
+
}
|
|
366
|
+
let next = this.#peek();
|
|
367
|
+
if (next === null) {
|
|
368
|
+
// End of incomplete object
|
|
369
|
+
if (this.#options.incompleteInputs) {
|
|
370
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
// Missing closing brace
|
|
374
|
+
yield Result.fromError(new Error("Expected `}` to end object, got end of input"));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Closing brace
|
|
378
|
+
if (next === '}') {
|
|
379
|
+
// End of object
|
|
380
|
+
this.#read();
|
|
381
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// Property
|
|
385
|
+
else {
|
|
386
|
+
for (let token of this.#readProperty()) {
|
|
387
|
+
if (token.isError) {
|
|
388
|
+
yield token;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
yield token;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
*#readBracelessObject(propertyNameTokens = null) {
|
|
397
|
+
// Start of object
|
|
398
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.StartObject));
|
|
399
|
+
// Initial tokens
|
|
400
|
+
if (propertyNameTokens !== null) {
|
|
401
|
+
for (let initialToken of this.#readProperty(propertyNameTokens)) {
|
|
402
|
+
if (initialToken.isError) {
|
|
403
|
+
yield initialToken;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
yield initialToken;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
while (true) {
|
|
410
|
+
// Comments & whitespace
|
|
411
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
412
|
+
if (token.isError) {
|
|
413
|
+
yield token;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
yield token;
|
|
417
|
+
}
|
|
418
|
+
if (this.#peek() === null) {
|
|
419
|
+
// End of braceless object
|
|
420
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
// Property
|
|
424
|
+
for (let token of this.#readProperty()) {
|
|
425
|
+
if (token.isError) {
|
|
426
|
+
yield token;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
yield token;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
*#readProperty(propertyNameTokens = null) {
|
|
434
|
+
// Property name
|
|
435
|
+
if (propertyNameTokens !== null) {
|
|
436
|
+
for (let token of propertyNameTokens) {
|
|
437
|
+
yield Result.fromValue(token);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
for (let token of this.#readPropertyName()) {
|
|
442
|
+
if (token.isError) {
|
|
443
|
+
yield token;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
yield token;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Comments & whitespace
|
|
450
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
451
|
+
if (token.isError) {
|
|
452
|
+
yield token;
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
yield token;
|
|
456
|
+
}
|
|
457
|
+
// Property value
|
|
458
|
+
for (let token of this.readElement()) {
|
|
459
|
+
if (token.isError) {
|
|
460
|
+
yield token;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
yield token;
|
|
464
|
+
}
|
|
465
|
+
// Comments & whitespace
|
|
466
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
467
|
+
if (token.isError) {
|
|
468
|
+
yield token;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
yield token;
|
|
472
|
+
}
|
|
473
|
+
// Optional comma
|
|
474
|
+
this.#readOne(',');
|
|
475
|
+
}
|
|
476
|
+
*#readPropertyName(string = null) {
|
|
477
|
+
// String
|
|
478
|
+
if (string === null) {
|
|
479
|
+
let stringToken = this.#readString();
|
|
480
|
+
if (stringToken.isError) {
|
|
481
|
+
yield stringToken;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
string = stringToken.value.value;
|
|
485
|
+
}
|
|
486
|
+
// Comments & whitespace
|
|
487
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
488
|
+
if (token.isError) {
|
|
489
|
+
yield token;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
yield token;
|
|
493
|
+
}
|
|
494
|
+
// Colon
|
|
495
|
+
if (!this.#readOne(':')) {
|
|
496
|
+
yield Result.fromError(new Error("Expected `:` after property name in object"));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// End of property name
|
|
500
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.PropertyName, string));
|
|
501
|
+
}
|
|
502
|
+
*#readArray() {
|
|
503
|
+
// Opening bracket
|
|
504
|
+
if (!this.#readOne('[')) {
|
|
505
|
+
yield Result.fromError(new Error("Expected `[` to start array"));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Start of array
|
|
509
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.StartArray));
|
|
510
|
+
while (true) {
|
|
511
|
+
// Comments & whitespace
|
|
512
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
513
|
+
if (token.isError) {
|
|
514
|
+
yield token;
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
yield token;
|
|
518
|
+
}
|
|
519
|
+
let next = this.#peek();
|
|
520
|
+
if (next === null) {
|
|
521
|
+
// End of incomplete array
|
|
522
|
+
if (this.#options.incompleteInputs) {
|
|
523
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.EndArray));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// Missing closing bracket
|
|
527
|
+
yield Result.fromError(new Error("Expected `]` to end array, got end of input"));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
// Closing bracket
|
|
531
|
+
if (next === ']') {
|
|
532
|
+
// End of array
|
|
533
|
+
this.#read();
|
|
534
|
+
yield Result.fromValue(new JsonhToken(JsonTokenType.EndArray));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Item
|
|
538
|
+
else {
|
|
539
|
+
for (let token of this.#readItem()) {
|
|
540
|
+
if (token.isError) {
|
|
541
|
+
yield token;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
yield token;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
*#readItem() {
|
|
550
|
+
// Element
|
|
551
|
+
for (let token of this.readElement()) {
|
|
552
|
+
if (token.isError) {
|
|
553
|
+
yield token;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
yield token;
|
|
557
|
+
}
|
|
558
|
+
// Comments & whitespace
|
|
559
|
+
for (let token of this.#readCommentsAndWhitespace()) {
|
|
560
|
+
if (token.isError) {
|
|
561
|
+
yield token;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
yield token;
|
|
565
|
+
}
|
|
566
|
+
// Optional comma
|
|
567
|
+
this.#readOne(',');
|
|
568
|
+
}
|
|
569
|
+
#readString() {
|
|
570
|
+
// Start quote
|
|
571
|
+
let startQuote = this.#readAny('"', '\'');
|
|
572
|
+
if (startQuote === null) {
|
|
573
|
+
return this.#readQuotelessString();
|
|
574
|
+
}
|
|
575
|
+
// Count multiple start quotes
|
|
576
|
+
let startQuoteCounter = 1;
|
|
577
|
+
while (this.#readOne(startQuote)) {
|
|
578
|
+
startQuoteCounter++;
|
|
579
|
+
}
|
|
580
|
+
// Empty string
|
|
581
|
+
if (startQuoteCounter === 2) {
|
|
582
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.String, ""));
|
|
583
|
+
}
|
|
584
|
+
// Count multiple end quotes
|
|
585
|
+
let endQuoteCounter = 0;
|
|
586
|
+
// Read string
|
|
587
|
+
let stringBuilder = "";
|
|
588
|
+
while (true) {
|
|
589
|
+
let next = this.#read();
|
|
590
|
+
if (next === null) {
|
|
591
|
+
return Result.fromError(new Error("Expected end of string, got end of input"));
|
|
592
|
+
}
|
|
593
|
+
// Partial end quote was actually part of string
|
|
594
|
+
if (next !== startQuote) {
|
|
595
|
+
stringBuilder += startQuote.repeat(endQuoteCounter);
|
|
596
|
+
endQuoteCounter = 0;
|
|
597
|
+
}
|
|
598
|
+
// End quote
|
|
599
|
+
if (next === startQuote) {
|
|
600
|
+
endQuoteCounter++;
|
|
601
|
+
if (endQuoteCounter === startQuoteCounter) {
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// Escape sequence
|
|
606
|
+
else if (next === '\\') {
|
|
607
|
+
let escapeSequenceResult = this.#readEscapeSequence();
|
|
608
|
+
if (escapeSequenceResult.isError) {
|
|
609
|
+
return Result.fromError(escapeSequenceResult.error);
|
|
610
|
+
}
|
|
611
|
+
stringBuilder += escapeSequenceResult.value;
|
|
612
|
+
}
|
|
613
|
+
// Literal character
|
|
614
|
+
else {
|
|
615
|
+
stringBuilder += next;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// Condition: skip remaining steps unless started with multiple quotes
|
|
619
|
+
if (startQuoteCounter > 1) {
|
|
620
|
+
// Pass 1: count leading whitespace -> newline
|
|
621
|
+
let hasLeadingWhitespaceNewline = false;
|
|
622
|
+
let leadingWhitespaceNewlineCounter = 0;
|
|
623
|
+
for (let index = 0; index < stringBuilder.length; index++) {
|
|
624
|
+
let next = stringBuilder.at(index);
|
|
625
|
+
// Newline
|
|
626
|
+
if (_a.#newlineChars.includes(next)) {
|
|
627
|
+
// Join CR LF
|
|
628
|
+
if (next === '\r' && index + 1 < stringBuilder.length && stringBuilder[index + 1] === '\n') {
|
|
629
|
+
index++;
|
|
630
|
+
}
|
|
631
|
+
hasLeadingWhitespaceNewline = true;
|
|
632
|
+
leadingWhitespaceNewlineCounter = index + 1;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
// Non-whitespace
|
|
636
|
+
else if (!_a.#whitespaceChars.includes(next)) {
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// Condition: skip remaining steps if pass 1 failed
|
|
641
|
+
if (hasLeadingWhitespaceNewline) {
|
|
642
|
+
// Pass 2: count trailing newline -> whitespace
|
|
643
|
+
let hasTrailingNewlineWhitespace = false;
|
|
644
|
+
let lastNewlineIndex = 0;
|
|
645
|
+
let trailingWhitespaceCounter = 0;
|
|
646
|
+
for (let index = 0; index < stringBuilder.length; index++) {
|
|
647
|
+
let next = stringBuilder.at(index);
|
|
648
|
+
// Newline
|
|
649
|
+
if (_a.#newlineChars.includes(next)) {
|
|
650
|
+
hasTrailingNewlineWhitespace = true;
|
|
651
|
+
lastNewlineIndex = index;
|
|
652
|
+
trailingWhitespaceCounter = 0;
|
|
653
|
+
// Join CR LF
|
|
654
|
+
if (next === '\r' && index + 1 < stringBuilder.length && stringBuilder[index + 1] === '\n') {
|
|
655
|
+
index++;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Whitespace
|
|
659
|
+
else if (_a.#whitespaceChars.includes(next)) {
|
|
660
|
+
trailingWhitespaceCounter++;
|
|
661
|
+
}
|
|
662
|
+
// Non-whitespace
|
|
663
|
+
else {
|
|
664
|
+
hasTrailingNewlineWhitespace = false;
|
|
665
|
+
trailingWhitespaceCounter = 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Condition: skip remaining steps if pass 2 failed
|
|
669
|
+
if (hasTrailingNewlineWhitespace) {
|
|
670
|
+
// Pass 3: strip trailing newline -> whitespace
|
|
671
|
+
stringBuilder = _a.#removeRange(stringBuilder, lastNewlineIndex, stringBuilder.length - lastNewlineIndex);
|
|
672
|
+
// Pass 4: strip leading whitespace -> newline
|
|
673
|
+
stringBuilder = _a.#removeRange(stringBuilder, 0, leadingWhitespaceNewlineCounter);
|
|
674
|
+
// Condition: skip remaining steps if no trailing whitespace
|
|
675
|
+
if (trailingWhitespaceCounter > 0) {
|
|
676
|
+
// Pass 5: strip line-leading whitespace
|
|
677
|
+
let isLineLeadingWhitespace = true;
|
|
678
|
+
let lineLeadingWhitespaceCounter = 0;
|
|
679
|
+
for (let index = 0; index < stringBuilder.length; index++) {
|
|
680
|
+
let next = stringBuilder.at(index);
|
|
681
|
+
// Newline
|
|
682
|
+
if (_a.#newlineChars.includes(next)) {
|
|
683
|
+
isLineLeadingWhitespace = true;
|
|
684
|
+
lineLeadingWhitespaceCounter = 0;
|
|
685
|
+
}
|
|
686
|
+
// Whitespace
|
|
687
|
+
else if (_a.#whitespaceChars.includes(next)) {
|
|
688
|
+
if (isLineLeadingWhitespace) {
|
|
689
|
+
// Increment line-leading whitespace
|
|
690
|
+
lineLeadingWhitespaceCounter++;
|
|
691
|
+
// Maximum line-leading whitespace reached
|
|
692
|
+
if (lineLeadingWhitespaceCounter === trailingWhitespaceCounter) {
|
|
693
|
+
// Remove line-leading whitespace
|
|
694
|
+
stringBuilder = _a.#removeRange(stringBuilder, index + 1 - lineLeadingWhitespaceCounter, lineLeadingWhitespaceCounter);
|
|
695
|
+
index -= lineLeadingWhitespaceCounter;
|
|
696
|
+
// Exit line-leading whitespace
|
|
697
|
+
isLineLeadingWhitespace = false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Non-whitespace
|
|
702
|
+
else {
|
|
703
|
+
if (isLineLeadingWhitespace) {
|
|
704
|
+
// Remove partial line-leading whitespace
|
|
705
|
+
stringBuilder = _a.#removeRange(stringBuilder, index - lineLeadingWhitespaceCounter, lineLeadingWhitespaceCounter);
|
|
706
|
+
index -= lineLeadingWhitespaceCounter;
|
|
707
|
+
// Exit line-leading whitespace
|
|
708
|
+
isLineLeadingWhitespace = false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// End of string
|
|
717
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.String, stringBuilder));
|
|
718
|
+
}
|
|
719
|
+
#readQuotelessString(initialChars = "") {
|
|
720
|
+
let isNamedLiteralPossible = true;
|
|
721
|
+
// Read quoteless string
|
|
722
|
+
let stringBuilder = initialChars;
|
|
723
|
+
while (true) {
|
|
724
|
+
// Peek char
|
|
725
|
+
let next = this.#peek();
|
|
726
|
+
if (next === null) {
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
// Escape sequence
|
|
730
|
+
if (next === '\\') {
|
|
731
|
+
this.#read();
|
|
732
|
+
let escapeSequenceResult = this.#readEscapeSequence();
|
|
733
|
+
if (escapeSequenceResult.isError) {
|
|
734
|
+
return Result.fromError(escapeSequenceResult.error);
|
|
735
|
+
}
|
|
736
|
+
stringBuilder += escapeSequenceResult.value;
|
|
737
|
+
isNamedLiteralPossible = false;
|
|
738
|
+
}
|
|
739
|
+
// End on reserved character
|
|
740
|
+
else if (_a.#reservedChars.includes(next)) {
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
// End on newline
|
|
744
|
+
else if (_a.#newlineChars.includes(next)) {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
// Literal character
|
|
748
|
+
else {
|
|
749
|
+
this.#read();
|
|
750
|
+
stringBuilder += next;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Ensure not empty
|
|
754
|
+
if (stringBuilder.length === 0) {
|
|
755
|
+
return Result.fromError(new Error("Empty quoteless string"));
|
|
756
|
+
}
|
|
757
|
+
// Trim whitespace
|
|
758
|
+
stringBuilder = _a.#trimAny(stringBuilder, _a.#whitespaceChars);
|
|
759
|
+
// Match named literal
|
|
760
|
+
if (isNamedLiteralPossible) {
|
|
761
|
+
if (stringBuilder === "null") {
|
|
762
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.Null));
|
|
763
|
+
}
|
|
764
|
+
else if (stringBuilder === "true") {
|
|
765
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.True));
|
|
766
|
+
}
|
|
767
|
+
else if (stringBuilder === "false") {
|
|
768
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.False));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// End of quoteless string
|
|
772
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.String, stringBuilder));
|
|
773
|
+
}
|
|
774
|
+
#detectQuotelessString() {
|
|
775
|
+
// Read whitespace
|
|
776
|
+
let whitespaceBuilder = "";
|
|
777
|
+
while (true) {
|
|
778
|
+
// Read char
|
|
779
|
+
let next = this.#peek();
|
|
780
|
+
if (next === null) {
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
// Newline
|
|
784
|
+
if (_a.#newlineChars.includes(next)) {
|
|
785
|
+
// Quoteless strings cannot contain unescaped newlines
|
|
786
|
+
return {
|
|
787
|
+
foundQuotelessString: false,
|
|
788
|
+
whitespaceChars: whitespaceBuilder
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
// End of whitespace
|
|
792
|
+
if (!_a.#whitespaceChars.includes(next)) {
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
// Whitespace
|
|
796
|
+
whitespaceBuilder += next;
|
|
797
|
+
this.#read();
|
|
798
|
+
}
|
|
799
|
+
// Found quoteless string if found backslash or non-reserved char
|
|
800
|
+
let nextChar = this.#peek();
|
|
801
|
+
return {
|
|
802
|
+
foundQuotelessString: nextChar !== null && (nextChar === '\\' || !_a.#reservedChars.includes(nextChar)),
|
|
803
|
+
whitespaceChars: whitespaceBuilder
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
#readNumber() {
|
|
807
|
+
// Read number
|
|
808
|
+
let numberBuilder = "";
|
|
809
|
+
// Read sign
|
|
810
|
+
let sign = this.#readAny('-', '+');
|
|
811
|
+
if (sign !== null) {
|
|
812
|
+
numberBuilder += sign;
|
|
813
|
+
}
|
|
814
|
+
// Read base
|
|
815
|
+
let baseDigits = "0123456789";
|
|
816
|
+
let hasBaseSpecifier = false;
|
|
817
|
+
if (this.#readOne('0')) {
|
|
818
|
+
numberBuilder += '0';
|
|
819
|
+
let hexBaseChar = this.#readAny('x', 'X');
|
|
820
|
+
if (hexBaseChar !== null) {
|
|
821
|
+
numberBuilder += hexBaseChar;
|
|
822
|
+
baseDigits = "0123456789abcdef";
|
|
823
|
+
hasBaseSpecifier = true;
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
let binaryBaseChar = this.#readAny('b', 'B');
|
|
827
|
+
if (binaryBaseChar !== null) {
|
|
828
|
+
numberBuilder += binaryBaseChar;
|
|
829
|
+
baseDigits = "01";
|
|
830
|
+
hasBaseSpecifier = true;
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
let octalBaseChar = this.#readAny('o', 'O');
|
|
834
|
+
if (octalBaseChar !== null) {
|
|
835
|
+
numberBuilder += octalBaseChar;
|
|
836
|
+
baseDigits = "01234567";
|
|
837
|
+
hasBaseSpecifier = true;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// Read main number
|
|
843
|
+
let mainResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
|
|
844
|
+
numberBuilder += mainResult.numberNoExponent;
|
|
845
|
+
if (mainResult.result.isError) {
|
|
846
|
+
return { numberToken: Result.fromError(mainResult.result.error), partialCharsRead: numberBuilder };
|
|
847
|
+
}
|
|
848
|
+
// Hexadecimal exponent
|
|
849
|
+
if (numberBuilder.at(-1) === 'e' || numberBuilder.at(-1) === 'E') {
|
|
850
|
+
// Read sign
|
|
851
|
+
let exponentSign = this.#readAny('-', '+');
|
|
852
|
+
if (exponentSign !== null) {
|
|
853
|
+
numberBuilder += exponentSign;
|
|
854
|
+
// Read exponent number
|
|
855
|
+
let exponentResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
|
|
856
|
+
numberBuilder += exponentResult.numberNoExponent;
|
|
857
|
+
if (exponentResult.result.isError) {
|
|
858
|
+
return { numberToken: Result.fromError(exponentResult.result.error), partialCharsRead: numberBuilder };
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// Exponent
|
|
863
|
+
else {
|
|
864
|
+
let exponentChar = this.#readAny('e', 'E');
|
|
865
|
+
if (exponentChar !== null) {
|
|
866
|
+
numberBuilder += exponentChar;
|
|
867
|
+
// Read sign
|
|
868
|
+
let exponentSign = this.#readAny('-', '+');
|
|
869
|
+
if (exponentSign !== null) {
|
|
870
|
+
numberBuilder += exponentSign;
|
|
871
|
+
}
|
|
872
|
+
// Read exponent number
|
|
873
|
+
let exponentResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
|
|
874
|
+
numberBuilder += exponentResult.numberNoExponent;
|
|
875
|
+
if (exponentResult.result.isError) {
|
|
876
|
+
return { numberToken: Result.fromError(exponentResult.result.error), partialCharsRead: numberBuilder };
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// End of number
|
|
881
|
+
return { numberToken: Result.fromValue(new JsonhToken(JsonTokenType.Number, numberBuilder)), partialCharsRead: "" };
|
|
882
|
+
}
|
|
883
|
+
#readNumberNoExponent(baseDigits, hasBaseSpecifier) {
|
|
884
|
+
let numberBuilder = "";
|
|
885
|
+
// Leading underscore
|
|
886
|
+
if (!hasBaseSpecifier && this.#peek() === '_') {
|
|
887
|
+
return { result: Result.fromError(new Error("Leading `_` in number")), numberNoExponent: numberBuilder };
|
|
888
|
+
}
|
|
889
|
+
let isFraction = false;
|
|
890
|
+
let isEmpty = true;
|
|
891
|
+
while (true) {
|
|
892
|
+
// Peek char
|
|
893
|
+
let next = this.#peek();
|
|
894
|
+
if (next === null) {
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
// Digit
|
|
898
|
+
if (baseDigits.includes(next.toLowerCase())) {
|
|
899
|
+
this.#read();
|
|
900
|
+
numberBuilder += next;
|
|
901
|
+
isEmpty = false;
|
|
902
|
+
}
|
|
903
|
+
// Dot
|
|
904
|
+
else if (next === '.') {
|
|
905
|
+
this.#read();
|
|
906
|
+
numberBuilder += next;
|
|
907
|
+
isEmpty = false;
|
|
908
|
+
// Duplicate dot
|
|
909
|
+
if (isFraction) {
|
|
910
|
+
return { result: Result.fromError(new Error("Duplicate `.` in number")), numberNoExponent: numberBuilder };
|
|
911
|
+
}
|
|
912
|
+
isFraction = true;
|
|
913
|
+
}
|
|
914
|
+
// Underscore
|
|
915
|
+
else if (next === '_') {
|
|
916
|
+
this.#read();
|
|
917
|
+
numberBuilder += next;
|
|
918
|
+
isEmpty = false;
|
|
919
|
+
}
|
|
920
|
+
// Other
|
|
921
|
+
else {
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Ensure not empty
|
|
926
|
+
if (isEmpty) {
|
|
927
|
+
return { result: Result.fromError(new Error("Empty number")), numberNoExponent: numberBuilder };
|
|
928
|
+
}
|
|
929
|
+
// Ensure at least one digit
|
|
930
|
+
if (!_a.#containsAnyExcept(numberBuilder, ['.', '-', '+', '_'])) {
|
|
931
|
+
return { result: Result.fromError(new Error("Number must have at least one digit")), numberNoExponent: numberBuilder };
|
|
932
|
+
}
|
|
933
|
+
// Trailing underscore
|
|
934
|
+
if (numberBuilder.endsWith('_')) {
|
|
935
|
+
return { result: Result.fromError(new Error("Trailing `_` in number")), numberNoExponent: numberBuilder };
|
|
936
|
+
}
|
|
937
|
+
// End of number
|
|
938
|
+
return { result: Result.fromValue(), numberNoExponent: numberBuilder };
|
|
939
|
+
}
|
|
940
|
+
#readNumberOrQuotelessString() {
|
|
941
|
+
// Read number
|
|
942
|
+
let number = this.#readNumber();
|
|
943
|
+
if (!number.numberToken.isError) {
|
|
944
|
+
// Try read quoteless string starting with number
|
|
945
|
+
let detectQuotelessStringResult = this.#detectQuotelessString();
|
|
946
|
+
if (detectQuotelessStringResult.foundQuotelessString) {
|
|
947
|
+
return this.#readQuotelessString(number.numberToken.value.value + detectQuotelessStringResult.whitespaceChars);
|
|
948
|
+
}
|
|
949
|
+
// Otherwise, accept number
|
|
950
|
+
else {
|
|
951
|
+
return number.numberToken;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// Read quoteless string starting with malformed number
|
|
955
|
+
else {
|
|
956
|
+
return this.#readQuotelessString(number.partialCharsRead);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
#readPrimitiveElement() {
|
|
960
|
+
// Peek char
|
|
961
|
+
let next = this.#peek();
|
|
962
|
+
if (next === null) {
|
|
963
|
+
return Result.fromError(new Error("Expected primitive element, got end of input"));
|
|
964
|
+
}
|
|
965
|
+
// Number
|
|
966
|
+
if (next.length === 1 && ((next >= '0' && next <= '9') || (next === '-' || next === '+') || next === '.')) {
|
|
967
|
+
return this.#readNumberOrQuotelessString();
|
|
968
|
+
}
|
|
969
|
+
// String
|
|
970
|
+
else if (next === '"' || next === '\'') {
|
|
971
|
+
return this.#readString();
|
|
972
|
+
}
|
|
973
|
+
// Quoteless string (or named literal)
|
|
974
|
+
else {
|
|
975
|
+
return this.#readQuotelessString();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
*#readCommentsAndWhitespace() {
|
|
979
|
+
while (true) {
|
|
980
|
+
// Whitespace
|
|
981
|
+
this.#readWhitespace();
|
|
982
|
+
// Peek char
|
|
983
|
+
let next = this.#peek();
|
|
984
|
+
if (next === null) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
// Comment
|
|
988
|
+
if (next === '#' || next === '/') {
|
|
989
|
+
yield this.#readComment();
|
|
990
|
+
}
|
|
991
|
+
// End of comments
|
|
992
|
+
else {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
#readComment() {
|
|
998
|
+
let blockComment = false;
|
|
999
|
+
// Hash-style comment
|
|
1000
|
+
if (this.#readOne('#')) {
|
|
1001
|
+
}
|
|
1002
|
+
else if (this.#readOne('/')) {
|
|
1003
|
+
// Line-style comment
|
|
1004
|
+
if (this.#readOne('/')) {
|
|
1005
|
+
}
|
|
1006
|
+
// Block-style comment
|
|
1007
|
+
else if (this.#readOne('*')) {
|
|
1008
|
+
blockComment = true;
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
return Result.fromError(new Error("Unexpected `/`"));
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
return Result.fromError(new Error("Unexpected character"));
|
|
1016
|
+
}
|
|
1017
|
+
// Read comment
|
|
1018
|
+
let commentBuilder = "";
|
|
1019
|
+
while (true) {
|
|
1020
|
+
// Read char
|
|
1021
|
+
let next = this.#read();
|
|
1022
|
+
if (blockComment) {
|
|
1023
|
+
// Error
|
|
1024
|
+
if (next === null) {
|
|
1025
|
+
return Result.fromError(new Error("Expected end of block comment, got end of input"));
|
|
1026
|
+
}
|
|
1027
|
+
// End of block comment
|
|
1028
|
+
if (next === '*' && this.#readOne('/')) {
|
|
1029
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.Comment, commentBuilder));
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
// End of line comment
|
|
1034
|
+
if (next === null || _a.#newlineChars.includes(next)) {
|
|
1035
|
+
return Result.fromValue(new JsonhToken(JsonTokenType.Comment, commentBuilder));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
// Comment char
|
|
1039
|
+
commentBuilder += next;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
#readWhitespace() {
|
|
1043
|
+
while (true) {
|
|
1044
|
+
// Peek char
|
|
1045
|
+
let next = this.#peek();
|
|
1046
|
+
if (next === null) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
// Whitespace
|
|
1050
|
+
if (_a.#whitespaceChars.includes(next)) {
|
|
1051
|
+
this.#read();
|
|
1052
|
+
}
|
|
1053
|
+
// End of whitespace
|
|
1054
|
+
else {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
#readHexSequence(length) {
|
|
1060
|
+
let hexChars = "";
|
|
1061
|
+
for (let index = 0; index < length; index++) {
|
|
1062
|
+
let next = this.#read();
|
|
1063
|
+
// Hex digit
|
|
1064
|
+
if (next !== null && ((next >= "0" && next <= "9") || (next >= "A" && next <= "F") || (next >= "a" && next <= "f"))) {
|
|
1065
|
+
hexChars += next;
|
|
1066
|
+
}
|
|
1067
|
+
// Unexpected char
|
|
1068
|
+
else {
|
|
1069
|
+
return Result.fromError(new Error("Incorrect number of hexadecimal digits in unicode escape sequence"));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
// Parse unicode character from hex digits
|
|
1073
|
+
return Result.fromValue(Number.parseInt(hexChars, 16));
|
|
1074
|
+
}
|
|
1075
|
+
#readEscapeSequence() {
|
|
1076
|
+
let escapeChar = this.#read();
|
|
1077
|
+
if (escapeChar === null) {
|
|
1078
|
+
return Result.fromError(new Error("Expected escape sequence, got end of input"));
|
|
1079
|
+
}
|
|
1080
|
+
// Reverse solidus
|
|
1081
|
+
if (escapeChar === '\\') {
|
|
1082
|
+
return Result.fromValue('\\');
|
|
1083
|
+
}
|
|
1084
|
+
// Backspace
|
|
1085
|
+
else if (escapeChar === 'b') {
|
|
1086
|
+
return Result.fromValue('\b');
|
|
1087
|
+
}
|
|
1088
|
+
// Form feed
|
|
1089
|
+
else if (escapeChar === 'f') {
|
|
1090
|
+
return Result.fromValue('\f');
|
|
1091
|
+
}
|
|
1092
|
+
// Newline
|
|
1093
|
+
else if (escapeChar === 'n') {
|
|
1094
|
+
return Result.fromValue('\n');
|
|
1095
|
+
}
|
|
1096
|
+
// Carriage return
|
|
1097
|
+
else if (escapeChar === 'r') {
|
|
1098
|
+
return Result.fromValue('\r');
|
|
1099
|
+
}
|
|
1100
|
+
// Tab
|
|
1101
|
+
else if (escapeChar === 't') {
|
|
1102
|
+
return Result.fromValue('\t');
|
|
1103
|
+
}
|
|
1104
|
+
// Vertical tab
|
|
1105
|
+
else if (escapeChar === 'v') {
|
|
1106
|
+
return Result.fromValue('\v');
|
|
1107
|
+
}
|
|
1108
|
+
// Null
|
|
1109
|
+
else if (escapeChar === '0') {
|
|
1110
|
+
return Result.fromValue('\0');
|
|
1111
|
+
}
|
|
1112
|
+
// Alert
|
|
1113
|
+
else if (escapeChar === 'a') {
|
|
1114
|
+
return Result.fromValue('\a');
|
|
1115
|
+
}
|
|
1116
|
+
// Escape
|
|
1117
|
+
else if (escapeChar === 'e') {
|
|
1118
|
+
return Result.fromValue('\u001b');
|
|
1119
|
+
}
|
|
1120
|
+
// Unicode hex sequence
|
|
1121
|
+
else if (escapeChar === 'u') {
|
|
1122
|
+
let hexSequence = this.#readHexSequence(4);
|
|
1123
|
+
if (hexSequence.isError) {
|
|
1124
|
+
return Result.fromError(hexSequence.error);
|
|
1125
|
+
}
|
|
1126
|
+
return Result.fromValue(String.fromCodePoint(hexSequence.value));
|
|
1127
|
+
}
|
|
1128
|
+
// Short unicode hex sequence
|
|
1129
|
+
else if (escapeChar === 'x') {
|
|
1130
|
+
let hexSequence = this.#readHexSequence(2);
|
|
1131
|
+
if (hexSequence.isError) {
|
|
1132
|
+
return Result.fromError(hexSequence.error);
|
|
1133
|
+
}
|
|
1134
|
+
return Result.fromValue(String.fromCodePoint(hexSequence.value));
|
|
1135
|
+
}
|
|
1136
|
+
// Long unicode hex sequence
|
|
1137
|
+
else if (escapeChar === 'U') {
|
|
1138
|
+
let hexSequence = this.#readHexSequence(8);
|
|
1139
|
+
if (hexSequence.isError) {
|
|
1140
|
+
return Result.fromError(hexSequence.error);
|
|
1141
|
+
}
|
|
1142
|
+
return Result.fromValue(String.fromCodePoint(hexSequence.value));
|
|
1143
|
+
}
|
|
1144
|
+
// Escaped newline
|
|
1145
|
+
else if (_a.#newlineChars.includes(escapeChar)) {
|
|
1146
|
+
// Join CR LF
|
|
1147
|
+
if (escapeChar === '\r') {
|
|
1148
|
+
this.#readOne('\n');
|
|
1149
|
+
}
|
|
1150
|
+
return Result.fromValue("");
|
|
1151
|
+
}
|
|
1152
|
+
// Other
|
|
1153
|
+
else {
|
|
1154
|
+
return Result.fromValue(escapeChar);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
#peek() {
|
|
1158
|
+
let next = this.#textReader.peek();
|
|
1159
|
+
if (next === null) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
return next;
|
|
1163
|
+
}
|
|
1164
|
+
#read() {
|
|
1165
|
+
let next = this.#textReader.read();
|
|
1166
|
+
if (next === null) {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
this.#charCounter++;
|
|
1170
|
+
return next;
|
|
1171
|
+
}
|
|
1172
|
+
#readOne(option) {
|
|
1173
|
+
if (this.#peek() === option) {
|
|
1174
|
+
this.#read();
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
#readAny(...options) {
|
|
1180
|
+
// Peek char
|
|
1181
|
+
let next = this.#peek();
|
|
1182
|
+
if (next === null) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
// Match option
|
|
1186
|
+
if (!options.includes(next)) {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
// Option matched
|
|
1190
|
+
this.#read();
|
|
1191
|
+
return next;
|
|
1192
|
+
}
|
|
1193
|
+
static #removeRange(input, start, count) {
|
|
1194
|
+
return input.slice(0, start) + input.slice(start + count);
|
|
1195
|
+
}
|
|
1196
|
+
static #trimAny(input, trimChars) {
|
|
1197
|
+
let start = 0;
|
|
1198
|
+
let end = input.length;
|
|
1199
|
+
while (start < end && trimChars.includes(input.at(start))) {
|
|
1200
|
+
start++;
|
|
1201
|
+
}
|
|
1202
|
+
while (end > start && trimChars.includes(input.at(end - 1))) {
|
|
1203
|
+
end--;
|
|
1204
|
+
}
|
|
1205
|
+
return input.slice(start, end);
|
|
1206
|
+
}
|
|
1207
|
+
static #containsAnyExcept(input, allowed) {
|
|
1208
|
+
for (let char of input) {
|
|
1209
|
+
if (!allowed.includes(char)) {
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
_a = JsonhReader;
|
|
1217
|
+
module.exports = JsonhReader;
|
|
1218
|
+
//# sourceMappingURL=jsonh-reader.js.map
|