mcbe-leveldb 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/DBUtils.js +114 -0
- package/DBUtils.js.map +1 -0
- package/DBUtils.ts +169 -0
- package/LevelUtils.js +1168 -0
- package/LevelUtils.js.map +1 -0
- package/LevelUtils.ts +1965 -0
- package/SNBTUtils.js +1374 -0
- package/SNBTUtils.js.map +1 -0
- package/SNBTUtils.ts +1725 -0
- package/__biome_data__.js +278 -0
- package/__biome_data__.js.map +1 -0
- package/__biome_data__.ts +277 -0
- package/index.js +6 -0
- package/index.js.map +1 -0
- package/index.ts +5 -0
- package/nbtSchemas.js +9211 -0
- package/nbtSchemas.js.map +1 -0
- package/nbtSchemas.ts +17213 -0
- package/package.json +60 -0
- package/tsconfig.json +24 -0
- package/tsconfig.production.json +25 -0
- package/types.d.ts +501 -0
- package/utils/JSONB.js +348 -0
- package/utils/JSONB.js.map +1 -0
- package/utils/JSONB.ts +566 -0
package/SNBTUtils.ts
ADDED
|
@@ -0,0 +1,1725 @@
|
|
|
1
|
+
import NBT, { type Compound } from "prismarine-nbt";
|
|
2
|
+
import type { OmitNeverValueKeys, VerifyConstraint } from "./types.js";
|
|
3
|
+
|
|
4
|
+
function toPrimitive(tag: any): number {
|
|
5
|
+
switch (tag.type) {
|
|
6
|
+
case "byte":
|
|
7
|
+
case "short":
|
|
8
|
+
case "int":
|
|
9
|
+
case "float":
|
|
10
|
+
case "double":
|
|
11
|
+
return tag.value;
|
|
12
|
+
case "long":
|
|
13
|
+
return Number(toLong(tag.value));
|
|
14
|
+
default:
|
|
15
|
+
throw new SyntaxError("Cannot convert to primitive: " + tag.type);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function uuidToIntArray(uuidStr: string): number[] {
|
|
20
|
+
const hex: string = uuidStr.replace(/-/g, "");
|
|
21
|
+
if (hex.length !== 32)
|
|
22
|
+
throw new SyntaxError("Invalid UUID: " + uuidStr, {
|
|
23
|
+
cause: {
|
|
24
|
+
position: 0,
|
|
25
|
+
stack: [
|
|
26
|
+
{
|
|
27
|
+
input: uuidStr,
|
|
28
|
+
positionInInput: 0,
|
|
29
|
+
function: "uuidToIntArray",
|
|
30
|
+
error: { type: "InvalidUUID", uuid: uuidStr },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
} as const satisfies SNBTParseErrorCause,
|
|
34
|
+
});
|
|
35
|
+
const arr: number[] = [];
|
|
36
|
+
for (let i: number = 0; i < 16; i += 4) {
|
|
37
|
+
arr.push((parseInt(hex.slice(i, i + 4), 16) << 16) >> 16); // 32-bit signed chunks
|
|
38
|
+
}
|
|
39
|
+
return arr;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SNBTParseErrorType =
|
|
43
|
+
| NonNullable<Extract<SNBTParseErrorCauseStackItem, { error?: any }>["error"]>["type"]
|
|
44
|
+
| "ExpectedEndOfInput"
|
|
45
|
+
| "ExpectedCompoundOrList";
|
|
46
|
+
|
|
47
|
+
export const SNBTParseErrorTypeToCode = {
|
|
48
|
+
DisallowedTypeInTypedArray: "disallowed-type-in-typed-array",
|
|
49
|
+
ExpectedCompoundOrList: "expected-compound-or-list",
|
|
50
|
+
InvalidArgumentToFunction: "invalid-argument-to-function",
|
|
51
|
+
InvalidSNBTKey: "invalid-snbt-key",
|
|
52
|
+
InvalidSNBTString: "invalid-snbt-string",
|
|
53
|
+
InvalidUUID: "invalid-uuid",
|
|
54
|
+
UnsupportedFunction: "unsupported-function",
|
|
55
|
+
UnsupportedSNBTPrimitive: "unsupported-snbt-primitive",
|
|
56
|
+
MixedListTypesNotAllowed: "mixed-list-types-not-allowed",
|
|
57
|
+
UnsupportedTypeInTypedArray: "unsupported-type-in-typed-array",
|
|
58
|
+
ExpectedEndOfInput: "expected-end-of-input",
|
|
59
|
+
} as const satisfies Record<SNBTParseErrorType, string>;
|
|
60
|
+
|
|
61
|
+
export const SNBTParseErrorDisplayNamespace = "mcbe-leveldb";
|
|
62
|
+
|
|
63
|
+
export type SNBTParseErrorCauseStackItem =
|
|
64
|
+
| {
|
|
65
|
+
positionInInput: number;
|
|
66
|
+
input: any;
|
|
67
|
+
function: "parseSNBTPrimitive";
|
|
68
|
+
error?:
|
|
69
|
+
| {
|
|
70
|
+
type: "InvalidArgumentToFunction";
|
|
71
|
+
functionName: "bool" | "uuid";
|
|
72
|
+
argument: string;
|
|
73
|
+
}
|
|
74
|
+
| {
|
|
75
|
+
type: "UnsupportedFunction";
|
|
76
|
+
functionName: string;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
type: "UnsupportedSNBTPrimitive";
|
|
80
|
+
raw: any;
|
|
81
|
+
}
|
|
82
|
+
| {
|
|
83
|
+
type: "InvalidSNBTString";
|
|
84
|
+
raw: any;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
| {
|
|
88
|
+
positionInInput: number;
|
|
89
|
+
input: string;
|
|
90
|
+
function: "uuidToIntArray";
|
|
91
|
+
error: {
|
|
92
|
+
type: "InvalidUUID";
|
|
93
|
+
uuid: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
| {
|
|
97
|
+
positionInInputItem: number;
|
|
98
|
+
inputItems: string[];
|
|
99
|
+
inputItem: string;
|
|
100
|
+
inputItemIndex: number;
|
|
101
|
+
arrayType: `${TypedArrayLetterTypes.B | TypedArrayLetterTypes.S | TypedArrayLetterTypes.I | TypedArrayLetterTypes.L}`;
|
|
102
|
+
function: "parseTypedArray";
|
|
103
|
+
error?:
|
|
104
|
+
| {
|
|
105
|
+
type: "DisallowedTypeInTypedArray";
|
|
106
|
+
itemType: `${TypedArrayLetterTypes.B | TypedArrayLetterTypes.S | TypedArrayLetterTypes.I | TypedArrayLetterTypes.L}`;
|
|
107
|
+
allowedTypes: `${TypedArrayLetterTypes.B | TypedArrayLetterTypes.S | TypedArrayLetterTypes.I | TypedArrayLetterTypes.L}`[];
|
|
108
|
+
}
|
|
109
|
+
| {
|
|
110
|
+
type: "UnsupportedTypeInTypedArray";
|
|
111
|
+
/**
|
|
112
|
+
* Should only be undefined if the item is unable to be parsed.
|
|
113
|
+
*/
|
|
114
|
+
itemType?: `${NBT.TagType}` | undefined;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
positionInInputItem: number;
|
|
119
|
+
inputItems: string[];
|
|
120
|
+
inputItem: string;
|
|
121
|
+
inputItemIndex: number;
|
|
122
|
+
function: "parseList";
|
|
123
|
+
error?: {
|
|
124
|
+
type: "MixedListTypesNotAllowed";
|
|
125
|
+
itemType: `${NBT.TagType}`;
|
|
126
|
+
item: NBT.Tags[NBT.TagType];
|
|
127
|
+
detectedArrayType: `${NBT.TagType}`;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
| {
|
|
131
|
+
positionInInput: number;
|
|
132
|
+
input: string;
|
|
133
|
+
key?: string;
|
|
134
|
+
function: "extractSNBT" | "parseSNBTCompoundString";
|
|
135
|
+
error?: {
|
|
136
|
+
type: "InvalidSNBTKey";
|
|
137
|
+
raw: any;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export interface SNBTParseErrorCause {
|
|
142
|
+
/**
|
|
143
|
+
* The position of the error in the input.
|
|
144
|
+
*/
|
|
145
|
+
position: number;
|
|
146
|
+
/**
|
|
147
|
+
* The stack trace of the error.
|
|
148
|
+
*/
|
|
149
|
+
stack: [initialError: SNBTParseErrorCauseStackItem, ...outerStack: SNBTParseErrorCauseStackItem[]];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Represents an error that occurred while parsing SNBT.
|
|
154
|
+
*
|
|
155
|
+
* @template T Whether the error's full stack has been resolved.
|
|
156
|
+
*/
|
|
157
|
+
export interface SNBTParseError<T extends boolean = boolean> extends Error {
|
|
158
|
+
/**
|
|
159
|
+
* The cause of the error.
|
|
160
|
+
*/
|
|
161
|
+
cause: SNBTParseErrorCause;
|
|
162
|
+
/**
|
|
163
|
+
* Whether the error's full stack has been resolved.
|
|
164
|
+
*/
|
|
165
|
+
isResolved: T;
|
|
166
|
+
/**
|
|
167
|
+
* The original input that caused the error.
|
|
168
|
+
*/
|
|
169
|
+
originalInput: T extends true ? string : null;
|
|
170
|
+
/**
|
|
171
|
+
* Gets the position of the error in the input.
|
|
172
|
+
*
|
|
173
|
+
* @returns The position of the error in the input.
|
|
174
|
+
*/
|
|
175
|
+
getErrorPosition(): T extends true ? [line: number, column: number] : null;
|
|
176
|
+
/**
|
|
177
|
+
* Gets the end position of the error in the input.
|
|
178
|
+
*
|
|
179
|
+
* @returns The end position of the error in the input.
|
|
180
|
+
*/
|
|
181
|
+
getErrorEndPosition(): T extends true ? [line: number, column: number] : null;
|
|
182
|
+
/**
|
|
183
|
+
* Gets the position of the error in the input with an offset.
|
|
184
|
+
*
|
|
185
|
+
* @param offset The offset to add to the error position.
|
|
186
|
+
* @returns The position of the error in the input.
|
|
187
|
+
*/
|
|
188
|
+
getErrorPositionWithOffset(offset: number): T extends true ? [line: number, column: number] : null;
|
|
189
|
+
/**
|
|
190
|
+
* Gets the position of the error in the input for a stack item.
|
|
191
|
+
*
|
|
192
|
+
* @param stackItem The stack item to get the error position for.
|
|
193
|
+
* @returns The position of the error in the input.
|
|
194
|
+
*/
|
|
195
|
+
getErrorPositionForStackItem(stackItem: SNBTParseErrorCauseStackItem): [line: number, column: number];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface SNBTParseErrorOptions extends ErrorOptions {
|
|
199
|
+
/**
|
|
200
|
+
* The cause of the error.
|
|
201
|
+
*/
|
|
202
|
+
cause: SNBTParseErrorCause;
|
|
203
|
+
/**
|
|
204
|
+
* Whether the error's full stack has been resolved.
|
|
205
|
+
*
|
|
206
|
+
* Only set this to true if this was from a function that was not called by another SNBT parser function, if the SNBT parser has more to the stack, this should be false.
|
|
207
|
+
*
|
|
208
|
+
* @defaul false
|
|
209
|
+
*/
|
|
210
|
+
resolved?: boolean | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* Only set this if {@link resolved} is also true, this is the original input of the SNBT parser function.
|
|
213
|
+
*/
|
|
214
|
+
originalInput?: string | undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface SNBTParseErrorConstructor extends Omit<ErrorConstructor, "prototype"> {
|
|
218
|
+
new (message: string, options: SNBTParseErrorOptions): SNBTParseError;
|
|
219
|
+
(message: string, options: SNBTParseErrorOptions): SNBTParseError;
|
|
220
|
+
prototype: SNBTParseError;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export var SNBTParseError: SNBTParseErrorConstructor = new Proxy(
|
|
224
|
+
class SNBTParseError extends Error implements SNBTParseError {
|
|
225
|
+
public declare cause: SNBTParseErrorCause;
|
|
226
|
+
public isResolved: boolean = false;
|
|
227
|
+
public originalInput: string | null = null;
|
|
228
|
+
public constructor(message: string, options: SNBTParseErrorOptions) {
|
|
229
|
+
if (arguments.length !== 2) throw new TypeError(`Incorrect number of arguments to constructor, expected 2 but got ${arguments.length} instead.`);
|
|
230
|
+
if (typeof message !== "string") throw new TypeError(`Expected args[0] (message) to be a string but got ${typeof message} instead.`);
|
|
231
|
+
if (typeof options !== "object" || options === null)
|
|
232
|
+
throw new TypeError(`Expected args[1] (options) to be an object but got ${options === null ? "null" : typeof options} instead.`);
|
|
233
|
+
if (typeof options.cause !== "object" || options.cause === null)
|
|
234
|
+
throw new TypeError(`Expected options.cause to be an object but got ${options === null ? "null" : typeof options.cause} instead.`);
|
|
235
|
+
super(message, options);
|
|
236
|
+
this.isResolved = options.resolved ?? false;
|
|
237
|
+
this.originalInput = options.originalInput ?? null;
|
|
238
|
+
}
|
|
239
|
+
public getErrorPosition(): [line: number, column: number] | null {
|
|
240
|
+
if (!this.isResolved || this.originalInput === null) return null;
|
|
241
|
+
const cause: SNBTParseErrorCause = this.cause;
|
|
242
|
+
const position: SNBTParseErrorCause["position"] = cause.position;
|
|
243
|
+
const input: string = this.originalInput;
|
|
244
|
+
return [input.slice(0, position).split("\n").length, position - input.slice(0, position).lastIndexOf("\n") + 1];
|
|
245
|
+
}
|
|
246
|
+
public getErrorEndPosition(): [line: number, column: number] | null {
|
|
247
|
+
if (!this.isResolved || this.originalInput === null) return null;
|
|
248
|
+
const cause: SNBTParseErrorCause = this.cause;
|
|
249
|
+
let offset: number = 0;
|
|
250
|
+
const stackItem: SNBTParseErrorCauseStackItem = cause.stack[0];
|
|
251
|
+
stackItemFunctionSwitcher: switch (stackItem.function) {
|
|
252
|
+
case "extractSNBT":
|
|
253
|
+
switch (stackItem.error?.type) {
|
|
254
|
+
case "InvalidSNBTKey":
|
|
255
|
+
offset = stackItem.error.raw.length;
|
|
256
|
+
break stackItemFunctionSwitcher;
|
|
257
|
+
default:
|
|
258
|
+
offset = stackItem.input.length - stackItem.positionInInput;
|
|
259
|
+
break stackItemFunctionSwitcher;
|
|
260
|
+
}
|
|
261
|
+
case "parseList":
|
|
262
|
+
switch (stackItem.error?.type) {
|
|
263
|
+
case "MixedListTypesNotAllowed":
|
|
264
|
+
offset = stackItem.inputItem.length - stackItem.positionInInputItem;
|
|
265
|
+
break stackItemFunctionSwitcher;
|
|
266
|
+
default:
|
|
267
|
+
offset = stackItem.inputItem.length - stackItem.positionInInputItem;
|
|
268
|
+
break stackItemFunctionSwitcher;
|
|
269
|
+
}
|
|
270
|
+
case "parseSNBTCompoundString":
|
|
271
|
+
switch (stackItem.error?.type) {
|
|
272
|
+
case "InvalidSNBTKey":
|
|
273
|
+
offset = stackItem.error.raw.length;
|
|
274
|
+
break stackItemFunctionSwitcher;
|
|
275
|
+
default:
|
|
276
|
+
offset = stackItem.input.length - stackItem.positionInInput;
|
|
277
|
+
break stackItemFunctionSwitcher;
|
|
278
|
+
}
|
|
279
|
+
case "parseSNBTPrimitive":
|
|
280
|
+
switch (stackItem.error?.type) {
|
|
281
|
+
case "InvalidArgumentToFunction":
|
|
282
|
+
offset = stackItem.error.argument.length;
|
|
283
|
+
break stackItemFunctionSwitcher;
|
|
284
|
+
case "UnsupportedFunction":
|
|
285
|
+
offset = stackItem.error.functionName.length;
|
|
286
|
+
break stackItemFunctionSwitcher;
|
|
287
|
+
case "UnsupportedSNBTPrimitive":
|
|
288
|
+
offset = stackItem.error.raw.length;
|
|
289
|
+
break stackItemFunctionSwitcher;
|
|
290
|
+
case "InvalidSNBTString":
|
|
291
|
+
offset = stackItem.error.raw.length;
|
|
292
|
+
break stackItemFunctionSwitcher;
|
|
293
|
+
default:
|
|
294
|
+
offset = stackItem.input.length - stackItem.positionInInput;
|
|
295
|
+
break stackItemFunctionSwitcher;
|
|
296
|
+
}
|
|
297
|
+
case "parseTypedArray":
|
|
298
|
+
switch (stackItem.error?.type) {
|
|
299
|
+
case "DisallowedTypeInTypedArray":
|
|
300
|
+
offset = stackItem.inputItem.length - stackItem.positionInInputItem;
|
|
301
|
+
break stackItemFunctionSwitcher;
|
|
302
|
+
case "UnsupportedTypeInTypedArray":
|
|
303
|
+
offset = stackItem.inputItem.length - stackItem.positionInInputItem;
|
|
304
|
+
break stackItemFunctionSwitcher;
|
|
305
|
+
default:
|
|
306
|
+
offset = stackItem.inputItem.length - stackItem.positionInInputItem;
|
|
307
|
+
break stackItemFunctionSwitcher;
|
|
308
|
+
}
|
|
309
|
+
case "uuidToIntArray":
|
|
310
|
+
offset = stackItem.error.uuid.length;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const position: SNBTParseErrorCause["position"] = cause.position + offset;
|
|
314
|
+
const input: string = this.originalInput;
|
|
315
|
+
return [input.slice(0, position).split("\n").length, position - input.slice(0, position).lastIndexOf("\n") + 1];
|
|
316
|
+
}
|
|
317
|
+
public getErrorPositionWithOffset(offset: number): [line: number, column: number] | null {
|
|
318
|
+
if (!this.isResolved || this.originalInput === null) return null;
|
|
319
|
+
const cause: SNBTParseErrorCause = this.cause;
|
|
320
|
+
const position: SNBTParseErrorCause["position"] = cause.position + offset;
|
|
321
|
+
const input: string = this.originalInput;
|
|
322
|
+
return [input.slice(0, position).split("\n").length, position - input.slice(0, position).lastIndexOf("\n") + 1];
|
|
323
|
+
}
|
|
324
|
+
public getErrorPositionForStackItem(stackItem: SNBTParseErrorCauseStackItem): [line: number, column: number] {
|
|
325
|
+
const input: string = "input" in stackItem ? stackItem.input : stackItem.inputItem;
|
|
326
|
+
const position: number = "positionInInput" in stackItem ? stackItem.positionInInput : stackItem.positionInInputItem;
|
|
327
|
+
return [input.slice(0, position).split("\n").length, position - input.slice(0, position).lastIndexOf("\n") + 1];
|
|
328
|
+
}
|
|
329
|
+
} as unknown as SNBTParseErrorConstructor,
|
|
330
|
+
{
|
|
331
|
+
apply(target: SNBTParseErrorConstructor, thisArg: any, argumentsList: [any, any]): SNBTParseError {
|
|
332
|
+
return new target(...argumentsList);
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
interface ParseSNBTPrimitiveOptions extends ParseSNBTBaseOptions {}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Parses an SNBT-like primitive.
|
|
341
|
+
*
|
|
342
|
+
* @internal
|
|
343
|
+
*/
|
|
344
|
+
function parseSNBTPrimitive(
|
|
345
|
+
raw: any,
|
|
346
|
+
options: ParseSNBTPrimitiveOptions & { keepGoingAfterError: true }
|
|
347
|
+
): { value?: NBT.Tags[NBT.TagType]; errors: SNBTParseError[] };
|
|
348
|
+
function parseSNBTPrimitive(raw: any, options?: ParseSNBTPrimitiveOptions & { keepGoingAfterError?: false }): NBT.Tags[NBT.TagType];
|
|
349
|
+
function parseSNBTPrimitive(raw: any, options?: ParseSNBTPrimitiveOptions): NBT.Tags[NBT.TagType] | { value?: NBT.Tags[NBT.TagType]; errors: SNBTParseError[] };
|
|
350
|
+
function parseSNBTPrimitive(raw: any, options: ParseSNBTPrimitiveOptions = {}): NBT.Tags[NBT.TagType] | { value?: NBT.Tags[NBT.TagType]; errors: SNBTParseError[] } {
|
|
351
|
+
const errors: SNBTParseError[] = [];
|
|
352
|
+
function structureResult(val?: NBT.Tags[NBT.TagType]): ReturnType<typeof parseSNBTPrimitive> {
|
|
353
|
+
if (options.keepGoingAfterError) return { value: val, errors };
|
|
354
|
+
return val!;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
if (typeof raw === "string") {
|
|
358
|
+
const originalInput: string = raw;
|
|
359
|
+
raw = raw.trim();
|
|
360
|
+
|
|
361
|
+
const funcMatch: RegExpMatchArray | null = raw.match(/^(\w+)\((.*)\)$/s);
|
|
362
|
+
if (funcMatch) {
|
|
363
|
+
const fn: string = funcMatch[1]!;
|
|
364
|
+
const arg: string = funcMatch[2]!.trim();
|
|
365
|
+
switch (fn) {
|
|
366
|
+
case "bool": {
|
|
367
|
+
const baseVal = parseSNBTPrimitive(arg, options);
|
|
368
|
+
if ("errors" in baseVal) {
|
|
369
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
370
|
+
err.cause.stack.push({
|
|
371
|
+
input: originalInput,
|
|
372
|
+
positionInInput: originalInput.indexOf(raw) + fn.length + 1,
|
|
373
|
+
function: "parseSNBTPrimitive",
|
|
374
|
+
});
|
|
375
|
+
err.cause.position += originalInput.indexOf(raw) + fn.length + 1;
|
|
376
|
+
});
|
|
377
|
+
errors.push(...baseVal.errors);
|
|
378
|
+
}
|
|
379
|
+
const val = "errors" in baseVal ? baseVal.value : baseVal;
|
|
380
|
+
if (!val) return structureResult();
|
|
381
|
+
if (val.type === "short" || val.type === "int" || val.type === "long" || val.type === "float" || val.type === "double") {
|
|
382
|
+
const num: number = Number(toPrimitive(val));
|
|
383
|
+
return structureResult(NBT.byte(num === 0 ? 0 : 1));
|
|
384
|
+
}
|
|
385
|
+
if (val.type === "byte") return val;
|
|
386
|
+
throw new SNBTParseError(`Invalid argument to bool(): ${arg}`, {
|
|
387
|
+
cause: {
|
|
388
|
+
position: originalInput.indexOf(raw) + fn.length + 1,
|
|
389
|
+
stack: [
|
|
390
|
+
{
|
|
391
|
+
input: originalInput,
|
|
392
|
+
positionInInput: originalInput.indexOf(raw) + fn.length + 1,
|
|
393
|
+
function: "parseSNBTPrimitive",
|
|
394
|
+
error: { type: "InvalidArgumentToFunction", functionName: "bool", argument: arg },
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
case "uuid": {
|
|
401
|
+
const uuidStr: string = arg.replace(/^["']|["']$/g, "");
|
|
402
|
+
try {
|
|
403
|
+
const uuidIntArray: number[] = uuidToIntArray(uuidStr);
|
|
404
|
+
return structureResult(NBT.intArray(uuidIntArray));
|
|
405
|
+
} catch (e) {
|
|
406
|
+
if (e instanceof Error && e.cause) {
|
|
407
|
+
(e.cause as SNBTParseErrorCause).stack.push({
|
|
408
|
+
positionInInput: originalInput.indexOf(raw) + fn.length + 1,
|
|
409
|
+
input: originalInput,
|
|
410
|
+
function: "parseSNBTPrimitive",
|
|
411
|
+
error: {
|
|
412
|
+
type: "InvalidArgumentToFunction",
|
|
413
|
+
functionName: "uuid",
|
|
414
|
+
argument: arg,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
(e.cause as SNBTParseErrorCause).position += originalInput.indexOf(raw) + fn.length + 1;
|
|
418
|
+
}
|
|
419
|
+
throw e;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
default:
|
|
423
|
+
throw (
|
|
424
|
+
(new ReferenceError(`Unsupported SNBT function: ${fn}`),
|
|
425
|
+
{
|
|
426
|
+
cause: {
|
|
427
|
+
position: originalInput.indexOf(raw) + fn.length + 1,
|
|
428
|
+
stack: [
|
|
429
|
+
{
|
|
430
|
+
input: originalInput,
|
|
431
|
+
positionInInput: originalInput.indexOf(raw) + fn.length + 1,
|
|
432
|
+
function: "parseSNBTPrimitive",
|
|
433
|
+
error: { type: "UnsupportedFunction", functionName: fn },
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
} as const satisfies SNBTParseErrorCause,
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const arrMatch: RegExpMatchArray | null = raw.match(/^\[(B|S|I|L);\s*(.*?)\]$/is);
|
|
443
|
+
if (arrMatch) {
|
|
444
|
+
const type = arrMatch[1]!.toUpperCase() as "B" | "S" | "I" | "L";
|
|
445
|
+
const items: string[] = arrMatch[2]!.split(/\s*,\s*/).filter(Boolean);
|
|
446
|
+
const baseVal = parseTypedArray(type, items, options);
|
|
447
|
+
if ("errors" in baseVal) {
|
|
448
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
449
|
+
const lastStackItem = err.cause.stack.at(-1)! as Extract<SNBTParseErrorCauseStackItem, { function: "parseTypedArray" }>;
|
|
450
|
+
const baseOffset: number = arrMatch[2]!.match(new RegExp(`^(.*?\\s*(,\\s*|$)){${lastStackItem.inputItemIndex}}`))?.[0]?.length ?? 0;
|
|
451
|
+
err.cause.stack.push({
|
|
452
|
+
input: originalInput,
|
|
453
|
+
positionInInput: originalInput.indexOf(raw) + raw.indexOf(arrMatch[2]) + baseOffset,
|
|
454
|
+
function: "parseSNBTPrimitive",
|
|
455
|
+
});
|
|
456
|
+
err.cause.position += originalInput.indexOf(raw) + raw.indexOf(arrMatch[2]) + baseOffset;
|
|
457
|
+
});
|
|
458
|
+
errors.push(...baseVal.errors);
|
|
459
|
+
}
|
|
460
|
+
return structureResult("errors" in baseVal ? baseVal.value : baseVal);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const listMatch: RegExpMatchArray | null = raw.match(/^\[\s*(.*?)\]$/is);
|
|
464
|
+
if (listMatch) {
|
|
465
|
+
const baseVal = extractSNBT(raw, options);
|
|
466
|
+
if ("errors" in baseVal) {
|
|
467
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
468
|
+
err.cause.stack.push({
|
|
469
|
+
input: originalInput,
|
|
470
|
+
positionInInput: originalInput.indexOf(raw),
|
|
471
|
+
function: "parseSNBTPrimitive",
|
|
472
|
+
});
|
|
473
|
+
err.cause.position += originalInput.indexOf(raw);
|
|
474
|
+
});
|
|
475
|
+
errors.push(...baseVal.errors);
|
|
476
|
+
}
|
|
477
|
+
return structureResult("errors" in baseVal ? baseVal.value : baseVal.value);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const match: RegExpMatchArray | null = raw.match(/^([-+]?0x[\da-fA-F]+|0b[01]+|[-+]?\d*\.?\d*(?:[eE][-+]?\d+)?)([bsilfdBSILFD])?$/i);
|
|
481
|
+
if (match) {
|
|
482
|
+
const numStr: string = match[1]!;
|
|
483
|
+
const suffix: string | undefined = match[2]?.toLowerCase();
|
|
484
|
+
let value: number | bigint;
|
|
485
|
+
if (numStr.startsWith("0x")) value = parseInt(numStr, 16);
|
|
486
|
+
else if (numStr.startsWith("0b")) value = parseInt(numStr.slice(2), 2);
|
|
487
|
+
else if (suffix === "l") value = BigInt(numStr);
|
|
488
|
+
else value = Number(numStr);
|
|
489
|
+
|
|
490
|
+
switch (suffix) {
|
|
491
|
+
case "b":
|
|
492
|
+
return structureResult(NBT.byte(Number(value)));
|
|
493
|
+
case "s":
|
|
494
|
+
return structureResult(NBT.short(Number(value)));
|
|
495
|
+
case "i":
|
|
496
|
+
return structureResult(NBT.int(Number(value)));
|
|
497
|
+
case "l":
|
|
498
|
+
return structureResult(NBT.long(toLongParts(BigInt(value))));
|
|
499
|
+
case "f":
|
|
500
|
+
return structureResult(NBT.float(Number(value)));
|
|
501
|
+
case "d":
|
|
502
|
+
return structureResult(NBT.double(Number(value)));
|
|
503
|
+
default:
|
|
504
|
+
if (typeof value === "bigint") return structureResult(NBT.long(toLongParts(value)));
|
|
505
|
+
if (Number.isInteger(value)) return structureResult(NBT.int(value));
|
|
506
|
+
return structureResult(NBT.double(value));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (["true", "false"].includes(raw)) return structureResult(NBT.byte(+(raw === "true")));
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
return structureResult({
|
|
514
|
+
type: "string",
|
|
515
|
+
value: parseFormattedString(raw),
|
|
516
|
+
});
|
|
517
|
+
} catch {
|
|
518
|
+
const error = new SNBTParseError("Invalid SNBT string: " + raw, {
|
|
519
|
+
cause: {
|
|
520
|
+
position: originalInput.indexOf(raw),
|
|
521
|
+
stack: [
|
|
522
|
+
{
|
|
523
|
+
function: "parseSNBTPrimitive",
|
|
524
|
+
input: originalInput,
|
|
525
|
+
positionInInput: originalInput.indexOf(raw),
|
|
526
|
+
error: {
|
|
527
|
+
type: "InvalidSNBTString",
|
|
528
|
+
raw,
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
if (options.keepGoingAfterError) errors.push(error);
|
|
535
|
+
else throw error;
|
|
536
|
+
return structureResult();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof raw === "number") {
|
|
541
|
+
if (raw % 1 !== 0) return structureResult(NBT.double(raw));
|
|
542
|
+
return structureResult(NBT.int(raw));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (typeof raw === "bigint") return structureResult(NBT.long(toLongParts(raw)));
|
|
546
|
+
if (typeof raw === "boolean") return structureResult(NBT.byte(+raw));
|
|
547
|
+
|
|
548
|
+
throw new SNBTParseError("Unsupported SNBT primitive: " + raw, {
|
|
549
|
+
cause: {
|
|
550
|
+
position: 0,
|
|
551
|
+
stack: [
|
|
552
|
+
{
|
|
553
|
+
function: "parseSNBTPrimitive",
|
|
554
|
+
input: raw,
|
|
555
|
+
positionInInput: 0,
|
|
556
|
+
error: {
|
|
557
|
+
type: "UnsupportedSNBTPrimitive",
|
|
558
|
+
raw,
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
} catch (e) {
|
|
565
|
+
if (options.keepGoingAfterError && e instanceof SNBTParseError) errors.push(e);
|
|
566
|
+
else throw e;
|
|
567
|
+
return structureResult();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
enum TypedArrayLetterTypes {
|
|
572
|
+
B = "byte",
|
|
573
|
+
S = "short",
|
|
574
|
+
I = "int",
|
|
575
|
+
L = "long",
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
type TypedArray = NBT.Tags[NBT.TagType.ByteArray | NBT.TagType.ShortArray | NBT.TagType.IntArray | NBT.TagType.LongArray];
|
|
579
|
+
|
|
580
|
+
function parseTypedArray(
|
|
581
|
+
type: "B" | "S" | "I" | "L",
|
|
582
|
+
items: string[],
|
|
583
|
+
options: ParseSNBTBaseOptions & { keepGoingAfterError: true }
|
|
584
|
+
): { value: TypedArray; errors: SNBTParseError[] };
|
|
585
|
+
function parseTypedArray(type: "B" | "S" | "I" | "L", items: string[], options?: ParseSNBTBaseOptions & { keepGoingAfterError?: false }): TypedArray;
|
|
586
|
+
function parseTypedArray(
|
|
587
|
+
type: "B" | "S" | "I" | "L",
|
|
588
|
+
items: string[],
|
|
589
|
+
options?: ParseSNBTBaseOptions
|
|
590
|
+
): TypedArray | { value: TypedArray; errors: SNBTParseError[] };
|
|
591
|
+
function parseTypedArray(
|
|
592
|
+
type: "B" | "S" | "I" | "L",
|
|
593
|
+
items: string[],
|
|
594
|
+
options: ParseSNBTBaseOptions = {}
|
|
595
|
+
): TypedArray | { value: TypedArray; errors: SNBTParseError[] } {
|
|
596
|
+
const errors: SNBTParseError[] = [];
|
|
597
|
+
const values: any[] = [];
|
|
598
|
+
|
|
599
|
+
let i: number = 0;
|
|
600
|
+
for (const x of items) {
|
|
601
|
+
try {
|
|
602
|
+
if (/^-?\d+b$/i.test(x)) {
|
|
603
|
+
const n: number = Number(x.slice(0, -1));
|
|
604
|
+
if (type === "L") values.push(toLongParts(BigInt(n)));
|
|
605
|
+
else if (!["B", "S", "I", "L"].includes(type))
|
|
606
|
+
throw new SNBTParseError(`Byte value not allowed in ${TypedArrayLetterTypes[type]} array: ${x}`, {
|
|
607
|
+
cause: {
|
|
608
|
+
position: 0,
|
|
609
|
+
stack: [
|
|
610
|
+
{
|
|
611
|
+
function: "parseTypedArray",
|
|
612
|
+
inputItems: items,
|
|
613
|
+
inputItem: x,
|
|
614
|
+
inputItemIndex: i,
|
|
615
|
+
positionInInputItem: 0,
|
|
616
|
+
arrayType: TypedArrayLetterTypes[type],
|
|
617
|
+
error: {
|
|
618
|
+
type: "DisallowedTypeInTypedArray",
|
|
619
|
+
allowedTypes: [TypedArrayLetterTypes.B, TypedArrayLetterTypes.S, TypedArrayLetterTypes.I, TypedArrayLetterTypes.L],
|
|
620
|
+
itemType: NBT.TagType.Byte,
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
else values.push(n);
|
|
627
|
+
} else if (/^-?\d+s$/i.test(x)) {
|
|
628
|
+
const n: number = Number(x.slice(0, -1));
|
|
629
|
+
if (type === "L") values.push(toLongParts(BigInt(n)));
|
|
630
|
+
else if (!["S", "I", "L"].includes(type))
|
|
631
|
+
throw new SNBTParseError(`Short value not allowed in ${TypedArrayLetterTypes[type]} array: ${x}`, {
|
|
632
|
+
cause: {
|
|
633
|
+
position: 0,
|
|
634
|
+
stack: [
|
|
635
|
+
{
|
|
636
|
+
function: "parseTypedArray",
|
|
637
|
+
inputItems: items,
|
|
638
|
+
inputItem: x,
|
|
639
|
+
inputItemIndex: i,
|
|
640
|
+
positionInInputItem: 0,
|
|
641
|
+
arrayType: TypedArrayLetterTypes[type],
|
|
642
|
+
error: {
|
|
643
|
+
type: "DisallowedTypeInTypedArray",
|
|
644
|
+
allowedTypes: [TypedArrayLetterTypes.B, TypedArrayLetterTypes.S, TypedArrayLetterTypes.I, TypedArrayLetterTypes.L],
|
|
645
|
+
itemType: NBT.TagType.Short,
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
else values.push(n);
|
|
652
|
+
} else if (/^-?\d+l$/i.test(x)) {
|
|
653
|
+
if (type !== "L")
|
|
654
|
+
throw new SNBTParseError(`Long value not allowed in ${TypedArrayLetterTypes[type]} array: ${x}`, {
|
|
655
|
+
cause: {
|
|
656
|
+
position: 0,
|
|
657
|
+
stack: [
|
|
658
|
+
{
|
|
659
|
+
function: "parseTypedArray",
|
|
660
|
+
inputItems: items,
|
|
661
|
+
inputItem: x,
|
|
662
|
+
inputItemIndex: i,
|
|
663
|
+
positionInInputItem: 0,
|
|
664
|
+
arrayType: TypedArrayLetterTypes[type],
|
|
665
|
+
error: {
|
|
666
|
+
type: "DisallowedTypeInTypedArray",
|
|
667
|
+
allowedTypes: [TypedArrayLetterTypes.B, TypedArrayLetterTypes.S, TypedArrayLetterTypes.I, TypedArrayLetterTypes.L],
|
|
668
|
+
itemType: NBT.TagType.Long,
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
values.push(parseLong(x));
|
|
675
|
+
} else if (/^-?\d+i?$/.test(x)) {
|
|
676
|
+
const n: number = x.toLowerCase().endsWith("i") ? Number(x.slice(0, -1)) : Number(x);
|
|
677
|
+
if (type === "L") values.push(toLongParts(BigInt(n)));
|
|
678
|
+
else if (!["I", "L"].includes(type))
|
|
679
|
+
throw new SNBTParseError(`Int value not allowed in ${TypedArrayLetterTypes[type]} array: ${x}`, {
|
|
680
|
+
cause: {
|
|
681
|
+
position: 0,
|
|
682
|
+
stack: [
|
|
683
|
+
{
|
|
684
|
+
function: "parseTypedArray",
|
|
685
|
+
inputItems: items,
|
|
686
|
+
inputItem: x,
|
|
687
|
+
inputItemIndex: i,
|
|
688
|
+
positionInInputItem: 0,
|
|
689
|
+
arrayType: TypedArrayLetterTypes[type],
|
|
690
|
+
error: {
|
|
691
|
+
type: "DisallowedTypeInTypedArray",
|
|
692
|
+
allowedTypes: [TypedArrayLetterTypes.B, TypedArrayLetterTypes.S, TypedArrayLetterTypes.I, TypedArrayLetterTypes.L],
|
|
693
|
+
itemType: NBT.TagType.Int,
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
else values.push(n);
|
|
700
|
+
} else if (["true", "false"].includes(x)) {
|
|
701
|
+
values.push(+(x === "true"));
|
|
702
|
+
} else {
|
|
703
|
+
throw new SNBTParseError(`Unsupported value in ${TypedArrayLetterTypes[type]} array: ${x}`, {
|
|
704
|
+
cause: {
|
|
705
|
+
position: 0,
|
|
706
|
+
stack: [
|
|
707
|
+
{
|
|
708
|
+
function: "parseTypedArray",
|
|
709
|
+
inputItems: items,
|
|
710
|
+
inputItem: x,
|
|
711
|
+
inputItemIndex: i,
|
|
712
|
+
positionInInputItem: 0,
|
|
713
|
+
arrayType: TypedArrayLetterTypes[type],
|
|
714
|
+
error: {
|
|
715
|
+
type: "UnsupportedTypeInTypedArray",
|
|
716
|
+
itemType: ((): `${NBT.TagType}` | undefined => {
|
|
717
|
+
try {
|
|
718
|
+
return parseSNBTPrimitive(x).type;
|
|
719
|
+
} catch {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
})(),
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
} catch (e) {
|
|
730
|
+
if (options.keepGoingAfterError && e instanceof SNBTParseError) errors.push(e);
|
|
731
|
+
else throw e;
|
|
732
|
+
}
|
|
733
|
+
i++;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let result: TypedArray;
|
|
737
|
+
|
|
738
|
+
switch (type) {
|
|
739
|
+
case "B":
|
|
740
|
+
result = NBT.byteArray(values as number[]);
|
|
741
|
+
break;
|
|
742
|
+
case "S":
|
|
743
|
+
result = NBT.shortArray(values as number[]);
|
|
744
|
+
break;
|
|
745
|
+
case "I":
|
|
746
|
+
result = NBT.intArray(values as number[]);
|
|
747
|
+
break;
|
|
748
|
+
case "L":
|
|
749
|
+
result = NBT.longArray(values as [high: number, low: number][]);
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return options.keepGoingAfterError ? { value: result, errors } : result;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
interface ParseListOptions extends ParseSNBTBaseOptions {}
|
|
757
|
+
|
|
758
|
+
function parseList(items: string[], options: ParseListOptions & { keepGoingAfterError: true }): { value: NBT.List<NBT.TagType>; errors: SNBTParseError[] };
|
|
759
|
+
function parseList(items: string[], options?: ParseListOptions & { keepGoingAfterError?: false }): NBT.List<NBT.TagType>;
|
|
760
|
+
function parseList(items: string[], options?: ParseListOptions): NBT.List<NBT.TagType> | { value: NBT.List<NBT.TagType>; errors: SNBTParseError[] };
|
|
761
|
+
function parseList(items: string[], options: ParseListOptions = {}): NBT.List<NBT.TagType> | { value: NBT.List<NBT.TagType>; errors: SNBTParseError[] } {
|
|
762
|
+
console.log(items, items.length);
|
|
763
|
+
const errors: SNBTParseError[] = [];
|
|
764
|
+
let values: NBT.Tags[NBT.TagType][] = [];
|
|
765
|
+
let valueIndices: number[] = [];
|
|
766
|
+
let i: number = 0;
|
|
767
|
+
for (const x of items) {
|
|
768
|
+
try {
|
|
769
|
+
if (x.trim().startsWith("{")) {
|
|
770
|
+
const baseVal = parseSNBTCompoundString(x, options);
|
|
771
|
+
if ("errors" in baseVal) {
|
|
772
|
+
baseVal.errors.forEach((err) => {
|
|
773
|
+
err.cause.stack.push({
|
|
774
|
+
inputItem: x,
|
|
775
|
+
inputItemIndex: i,
|
|
776
|
+
inputItems: items,
|
|
777
|
+
positionInInputItem: 0,
|
|
778
|
+
function: "parseList",
|
|
779
|
+
});
|
|
780
|
+
err.cause.position += 0;
|
|
781
|
+
});
|
|
782
|
+
errors.push(...baseVal.errors);
|
|
783
|
+
}
|
|
784
|
+
values.push("errors" in baseVal ? baseVal.value : baseVal);
|
|
785
|
+
} else {
|
|
786
|
+
const baseVal = parseSNBTPrimitive(x, options);
|
|
787
|
+
if ("errors" in baseVal) {
|
|
788
|
+
baseVal.errors.forEach((err) => {
|
|
789
|
+
err.cause.stack.push({
|
|
790
|
+
inputItem: x,
|
|
791
|
+
inputItemIndex: i,
|
|
792
|
+
inputItems: items,
|
|
793
|
+
positionInInputItem: 0,
|
|
794
|
+
function: "parseList",
|
|
795
|
+
});
|
|
796
|
+
err.cause.position += 0;
|
|
797
|
+
});
|
|
798
|
+
errors.push(...baseVal.errors);
|
|
799
|
+
}
|
|
800
|
+
const val = "errors" in baseVal ? baseVal.value : baseVal;
|
|
801
|
+
if (val !== undefined) values.push(val);
|
|
802
|
+
}
|
|
803
|
+
valueIndices.push(i);
|
|
804
|
+
} catch (e) {
|
|
805
|
+
if (options.keepGoingAfterError && e instanceof SNBTParseError) errors.push(e);
|
|
806
|
+
else throw e;
|
|
807
|
+
}
|
|
808
|
+
i++;
|
|
809
|
+
}
|
|
810
|
+
let type: `${NBT.TagType}` | undefined = values[0]?.type;
|
|
811
|
+
if (
|
|
812
|
+
type &&
|
|
813
|
+
!values.every((v, i) => {
|
|
814
|
+
if (v.type === type) {
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
if (!(options.mixedListsAllowed ?? true)) {
|
|
818
|
+
if (!options.keepGoingAfterError)
|
|
819
|
+
throw new SNBTParseError("Mixed list types not allowed.", {
|
|
820
|
+
cause: {
|
|
821
|
+
position: 0,
|
|
822
|
+
stack: [
|
|
823
|
+
{
|
|
824
|
+
function: "parseList",
|
|
825
|
+
inputItems: items,
|
|
826
|
+
inputItem: items[i]!,
|
|
827
|
+
inputItemIndex: valueIndices[i]!,
|
|
828
|
+
positionInInputItem: 0,
|
|
829
|
+
error: {
|
|
830
|
+
type: "MixedListTypesNotAllowed",
|
|
831
|
+
itemType: v.type,
|
|
832
|
+
item: v,
|
|
833
|
+
detectedArrayType: type,
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
],
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return false;
|
|
841
|
+
})
|
|
842
|
+
) {
|
|
843
|
+
if (!(options.mixedListsAllowed ?? true) && options.keepGoingAfterError) {
|
|
844
|
+
const numOfEachType = values.reduce((acc, v) => ({ ...acc, [v.type]: (acc[v.type] ?? 0) + 1 }), {} as Record<`${NBT.TagType}`, number>);
|
|
845
|
+
const mostCommonType = Object.entries(numOfEachType).reduce((a, b) => (a[1] > b[1] ? a : b))[0] as `${NBT.TagType}`;
|
|
846
|
+
for (let i: number = 0; i < values.length; i++) {
|
|
847
|
+
if (values[i]!.type === mostCommonType) continue;
|
|
848
|
+
errors.push(
|
|
849
|
+
new SNBTParseError("Mixed list types not allowed.", {
|
|
850
|
+
cause: {
|
|
851
|
+
position: 0,
|
|
852
|
+
stack: [
|
|
853
|
+
{
|
|
854
|
+
function: "parseList",
|
|
855
|
+
inputItems: items,
|
|
856
|
+
inputItem: items[i]!,
|
|
857
|
+
inputItemIndex: valueIndices[i]!,
|
|
858
|
+
positionInInputItem: 0,
|
|
859
|
+
error: {
|
|
860
|
+
type: "MixedListTypesNotAllowed",
|
|
861
|
+
itemType: values[i]!.type,
|
|
862
|
+
item: values[i]!,
|
|
863
|
+
detectedArrayType: type,
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
},
|
|
868
|
+
})
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (options.convertMixedListsToCompoundLists ?? true) values = values.map((v, i) => ({ type: NBT.TagType.Compound, value: { "": v } }));
|
|
873
|
+
}
|
|
874
|
+
const result = NBT.list({ type: values[0]?.type ?? "end", value: values.map((v) => v.value) }) as any;
|
|
875
|
+
return options.keepGoingAfterError ? { value: result, errors } : result;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export interface ParseSNBTBaseOptions {
|
|
879
|
+
/**
|
|
880
|
+
* Whether to allow lists of mixed types.
|
|
881
|
+
*
|
|
882
|
+
* @default true
|
|
883
|
+
*/
|
|
884
|
+
mixedListsAllowed?: boolean;
|
|
885
|
+
/**
|
|
886
|
+
* Whether to convert lists of mixed types to compound lists.
|
|
887
|
+
*
|
|
888
|
+
* @default true
|
|
889
|
+
*/
|
|
890
|
+
convertMixedListsToCompoundLists?: boolean;
|
|
891
|
+
/**
|
|
892
|
+
* Whether the function is being called from within an inner stack of a parent SNBT parser function.
|
|
893
|
+
*
|
|
894
|
+
* This is internal only and should not be specified by users.
|
|
895
|
+
*
|
|
896
|
+
* @internal
|
|
897
|
+
* @ignore
|
|
898
|
+
*
|
|
899
|
+
* @default false
|
|
900
|
+
*/
|
|
901
|
+
isInnerStack?: boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Whether to keep parsing the SNBT string even if there are errors.
|
|
904
|
+
*
|
|
905
|
+
* It will cause the function to return an object containing the errors, as well as the parts of the value that were able to be extracted.
|
|
906
|
+
*
|
|
907
|
+
* @default false
|
|
908
|
+
*/
|
|
909
|
+
keepGoingAfterError?: boolean;
|
|
910
|
+
/**
|
|
911
|
+
* Whether to stop parsing the SNBT string at a negative depth.
|
|
912
|
+
*
|
|
913
|
+
* @default true
|
|
914
|
+
*/
|
|
915
|
+
stopAtNegativeDepth?: boolean;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Parse an SNBT compound string into Prismarine-NBT JSON.
|
|
920
|
+
*/
|
|
921
|
+
export function parseSNBTCompoundString(
|
|
922
|
+
input: string,
|
|
923
|
+
options: ParseSNBTBaseOptions & { keepGoingAfterError: true }
|
|
924
|
+
): { value: Compound; errors: SNBTParseError[] };
|
|
925
|
+
export function parseSNBTCompoundString(input: string, options?: ParseSNBTBaseOptions & { keepGoingAfterError?: false }): Compound;
|
|
926
|
+
export function parseSNBTCompoundString(input: string, options?: ParseSNBTBaseOptions): Compound | { value: Compound; errors: SNBTParseError[] };
|
|
927
|
+
export function parseSNBTCompoundString(input: string, options: ParseSNBTBaseOptions = {}): Compound | { value: Compound; errors: SNBTParseError[] } {
|
|
928
|
+
const errors: SNBTParseError[] = [];
|
|
929
|
+
const originalInput: string = input;
|
|
930
|
+
input = input.trim();
|
|
931
|
+
|
|
932
|
+
if (input.startsWith("{")) {
|
|
933
|
+
input = input.slice(1);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const result: Record<string, any> = {};
|
|
937
|
+
let depth: number = 0;
|
|
938
|
+
let currentValuePos: number = -1;
|
|
939
|
+
let currentKey: string = "";
|
|
940
|
+
let currentValue: string = "";
|
|
941
|
+
let inString: string | null = null;
|
|
942
|
+
|
|
943
|
+
function commitPair(): void {
|
|
944
|
+
if (!currentKey) return;
|
|
945
|
+
result[currentKey.trim()] = parseValue(currentValue.trim(), currentKey, currentValuePos);
|
|
946
|
+
currentKey = "";
|
|
947
|
+
currentValue = "";
|
|
948
|
+
currentValuePos = -1;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function parseValue(val: string, key: string, pos: number): NBT.Tags[NBT.TagType] | undefined {
|
|
952
|
+
const originalVal: string = val;
|
|
953
|
+
val = val.trim();
|
|
954
|
+
if (!val) return NBT.comp({});
|
|
955
|
+
|
|
956
|
+
if (val.startsWith("{") && val.endsWith("}")) {
|
|
957
|
+
const baseVal = parseSNBTCompoundString(val, { ...options, isInnerStack: true });
|
|
958
|
+
if ("errors" in baseVal) {
|
|
959
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
960
|
+
err.cause.stack.push({
|
|
961
|
+
input: originalInput,
|
|
962
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
963
|
+
key,
|
|
964
|
+
function: "extractSNBT",
|
|
965
|
+
});
|
|
966
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
967
|
+
});
|
|
968
|
+
errors.push(...baseVal.errors);
|
|
969
|
+
}
|
|
970
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (val.startsWith("[") && val.endsWith("]")) {
|
|
974
|
+
const baseVal = parseSNBTPrimitive(val, { ...options, isInnerStack: true });
|
|
975
|
+
if ("errors" in baseVal) {
|
|
976
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
977
|
+
err.cause.stack.push({
|
|
978
|
+
input: originalInput,
|
|
979
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
980
|
+
key,
|
|
981
|
+
function: "extractSNBT",
|
|
982
|
+
});
|
|
983
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
984
|
+
});
|
|
985
|
+
errors.push(...baseVal.errors);
|
|
986
|
+
}
|
|
987
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const baseVal = parseSNBTPrimitive(val, { ...options, isInnerStack: true });
|
|
991
|
+
if ("errors" in baseVal) {
|
|
992
|
+
baseVal.errors.forEach((err: SNBTParseError): void => {
|
|
993
|
+
err.cause.stack.push({
|
|
994
|
+
input: originalInput,
|
|
995
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
996
|
+
key,
|
|
997
|
+
function: "extractSNBT",
|
|
998
|
+
});
|
|
999
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
1000
|
+
});
|
|
1001
|
+
errors.push(...baseVal.errors);
|
|
1002
|
+
}
|
|
1003
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
let readingKey: boolean = true;
|
|
1007
|
+
for (let i: number = 0; i < input.length; i++) {
|
|
1008
|
+
if (depth < 0) break;
|
|
1009
|
+
const c: string | undefined = input[i];
|
|
1010
|
+
|
|
1011
|
+
if (c === '"' || c === "'") {
|
|
1012
|
+
if (inString === c) inString = null;
|
|
1013
|
+
else if (!inString) inString = c;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (!inString) {
|
|
1017
|
+
if (c === ":" && readingKey) {
|
|
1018
|
+
readingKey = false;
|
|
1019
|
+
continue;
|
|
1020
|
+
} else if (c === "," && depth === 0) {
|
|
1021
|
+
commitPair();
|
|
1022
|
+
readingKey = true;
|
|
1023
|
+
continue;
|
|
1024
|
+
} else if (c === "{" || c === "[") {
|
|
1025
|
+
depth++;
|
|
1026
|
+
} else if (c === "}" || c === "]") {
|
|
1027
|
+
depth--;
|
|
1028
|
+
if (depth === -1) {
|
|
1029
|
+
commitPair();
|
|
1030
|
+
readingKey = true;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
if ((options.stopAtNegativeDepth ?? true) && depth < 0) {
|
|
1034
|
+
i++;
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (readingKey) currentKey += c;
|
|
1041
|
+
else {
|
|
1042
|
+
currentValue += c;
|
|
1043
|
+
if (currentValuePos === -1 && depth === 0) currentValuePos = i;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
commitPair();
|
|
1048
|
+
|
|
1049
|
+
const val = NBT.comp(
|
|
1050
|
+
Object.fromEntries(
|
|
1051
|
+
Object.entries(result)
|
|
1052
|
+
.map(([k, v]: [string, any]): [string | undefined, any] => [
|
|
1053
|
+
((): string | undefined => {
|
|
1054
|
+
try {
|
|
1055
|
+
return parseFormattedKey(k.trim());
|
|
1056
|
+
} catch {
|
|
1057
|
+
const error = new SNBTParseError("Invalid SNBT key: " + k.trim(), {
|
|
1058
|
+
cause: {
|
|
1059
|
+
position: k.indexOf(k.trim()),
|
|
1060
|
+
stack: [
|
|
1061
|
+
{
|
|
1062
|
+
function: "parseSNBTCompoundString",
|
|
1063
|
+
input: k,
|
|
1064
|
+
positionInInput: k.indexOf(k.trim()),
|
|
1065
|
+
key: k.trim(),
|
|
1066
|
+
error: {
|
|
1067
|
+
type: "InvalidSNBTKey",
|
|
1068
|
+
raw: k.trim(),
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
},
|
|
1073
|
+
});
|
|
1074
|
+
if (options.keepGoingAfterError) errors.push(error);
|
|
1075
|
+
else throw error;
|
|
1076
|
+
return undefined;
|
|
1077
|
+
}
|
|
1078
|
+
})(),
|
|
1079
|
+
v,
|
|
1080
|
+
])
|
|
1081
|
+
.filter((v: [string | undefined, any]): v is [string, any] => v[0] !== undefined)
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
return options.keepGoingAfterError ? { value: val, errors } : val;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Parses an SNBT string into Prismarine-NBT JSON.
|
|
1090
|
+
*
|
|
1091
|
+
* @param input The SNBT string to parse.
|
|
1092
|
+
* @param options The options to use when parsing the SNBT string.
|
|
1093
|
+
* @returns An object containing the parsed value, start position, end position, remaining text, and errors.
|
|
1094
|
+
*/
|
|
1095
|
+
export function extractSNBT(
|
|
1096
|
+
input: string,
|
|
1097
|
+
options: ParseSNBTBaseOptions & { keepGoingAfterError: true }
|
|
1098
|
+
): { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string; errors: SNBTParseError[] };
|
|
1099
|
+
export function extractSNBT(
|
|
1100
|
+
input: string,
|
|
1101
|
+
options?: ParseSNBTBaseOptions & { keepGoingAfterError?: false }
|
|
1102
|
+
): { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string };
|
|
1103
|
+
export function extractSNBT(
|
|
1104
|
+
input: string,
|
|
1105
|
+
options?: ParseSNBTBaseOptions
|
|
1106
|
+
):
|
|
1107
|
+
| { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string }
|
|
1108
|
+
| { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string; errors: SNBTParseError[] };
|
|
1109
|
+
export function extractSNBT(
|
|
1110
|
+
input: string,
|
|
1111
|
+
options: ParseSNBTBaseOptions = {}
|
|
1112
|
+
):
|
|
1113
|
+
| { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string }
|
|
1114
|
+
| { value: Compound | NBT.List<NBT.TagType>; startPos: number; endPos: number; remaining: string; errors: SNBTParseError[] } {
|
|
1115
|
+
const errors: SNBTParseError[] = [];
|
|
1116
|
+
const originalInput: string = input;
|
|
1117
|
+
input = input.trim();
|
|
1118
|
+
let resultType: "list" | "compound" = "compound";
|
|
1119
|
+
|
|
1120
|
+
if (input.startsWith("{")) {
|
|
1121
|
+
input = input.slice(1);
|
|
1122
|
+
resultType = "compound";
|
|
1123
|
+
} else if (input.startsWith("[")) {
|
|
1124
|
+
input = input.slice(1);
|
|
1125
|
+
resultType = "list";
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const result: Record<string, any> = {};
|
|
1129
|
+
const listResult: [value: string, pos: number][] = [];
|
|
1130
|
+
let depth: number = 0;
|
|
1131
|
+
let currentKey: string = "";
|
|
1132
|
+
let currentValue: string = "";
|
|
1133
|
+
let currentValuePos: number = -1;
|
|
1134
|
+
let inString: string | null = null;
|
|
1135
|
+
|
|
1136
|
+
function commitPair(): void {
|
|
1137
|
+
if (resultType === "compound") {
|
|
1138
|
+
if (!currentKey) return;
|
|
1139
|
+
result[currentKey.trim()] = parseValue(currentValue.trim(), currentKey, currentValuePos);
|
|
1140
|
+
currentKey = "";
|
|
1141
|
+
currentValue = "";
|
|
1142
|
+
currentValuePos = -1;
|
|
1143
|
+
} else if (resultType === "list") {
|
|
1144
|
+
listResult.push([currentValue.trim(), currentValuePos + currentValue.indexOf(currentValue.trim())]);
|
|
1145
|
+
currentValue = "";
|
|
1146
|
+
currentValuePos = -1;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function parseValue(val: string, key: string, pos: number) {
|
|
1151
|
+
const originalVal: string = val;
|
|
1152
|
+
val = val.trim();
|
|
1153
|
+
if (!val) return NBT.comp({});
|
|
1154
|
+
|
|
1155
|
+
if (val.startsWith("{") && val.endsWith("}")) {
|
|
1156
|
+
const baseVal = parseSNBTCompoundString(val, { ...options, isInnerStack: true });
|
|
1157
|
+
if ("errors" in baseVal) {
|
|
1158
|
+
baseVal.errors.forEach((err) => {
|
|
1159
|
+
err.cause.stack.push({
|
|
1160
|
+
input: originalInput,
|
|
1161
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
1162
|
+
key,
|
|
1163
|
+
function: "extractSNBT",
|
|
1164
|
+
});
|
|
1165
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
1166
|
+
});
|
|
1167
|
+
errors.push(...baseVal.errors);
|
|
1168
|
+
}
|
|
1169
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (val.startsWith("[") && val.endsWith("]")) {
|
|
1173
|
+
const baseVal = parseSNBTPrimitive(val, { ...options, isInnerStack: true });
|
|
1174
|
+
if ("errors" in baseVal) {
|
|
1175
|
+
baseVal.errors.forEach((err) => {
|
|
1176
|
+
err.cause.stack.push({
|
|
1177
|
+
input: originalInput,
|
|
1178
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
1179
|
+
key,
|
|
1180
|
+
function: "extractSNBT",
|
|
1181
|
+
});
|
|
1182
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
1183
|
+
});
|
|
1184
|
+
errors.push(...baseVal.errors);
|
|
1185
|
+
}
|
|
1186
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const baseVal = parseSNBTPrimitive(val, { ...options, isInnerStack: true });
|
|
1190
|
+
if ("errors" in baseVal) {
|
|
1191
|
+
baseVal.errors.forEach((err) => {
|
|
1192
|
+
err.cause.stack.push({
|
|
1193
|
+
input: originalInput,
|
|
1194
|
+
positionInInput: originalInput.indexOf(input) + pos + originalVal.indexOf(val),
|
|
1195
|
+
key,
|
|
1196
|
+
function: "extractSNBT",
|
|
1197
|
+
});
|
|
1198
|
+
err.cause.position += originalInput.indexOf(input) + pos + originalVal.indexOf(val);
|
|
1199
|
+
});
|
|
1200
|
+
errors.push(...baseVal.errors);
|
|
1201
|
+
}
|
|
1202
|
+
return "errors" in baseVal ? baseVal.value : baseVal;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
let readingKey: boolean = resultType === "list" ? false : true;
|
|
1206
|
+
let bracketTypeStack: ("list" | "compound")[] = [resultType];
|
|
1207
|
+
let i: number = 0;
|
|
1208
|
+
for (i; i < input.length; i++) {
|
|
1209
|
+
const c: string | undefined = input[i];
|
|
1210
|
+
|
|
1211
|
+
if (c === '"' || c === "'") {
|
|
1212
|
+
if (inString === c) inString = null;
|
|
1213
|
+
else if (!inString) inString = c;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (!inString) {
|
|
1217
|
+
if (c === ":" && readingKey) {
|
|
1218
|
+
readingKey = false;
|
|
1219
|
+
continue;
|
|
1220
|
+
} else if (c === "," && depth === 0) {
|
|
1221
|
+
resultType !== "list" || currentValue.trim() !== "" ? commitPair() : ((currentKey = ""), (currentValue = ""), (currentValuePos = -1));
|
|
1222
|
+
if (resultType === "compound") readingKey = true;
|
|
1223
|
+
continue;
|
|
1224
|
+
} else if (c === "{" || c === "[") {
|
|
1225
|
+
bracketTypeStack.push(c === "{" ? "compound" : "list");
|
|
1226
|
+
depth++;
|
|
1227
|
+
} else if (c === "}" || c === "]") {
|
|
1228
|
+
bracketTypeStack.pop();
|
|
1229
|
+
depth--;
|
|
1230
|
+
if ((options.stopAtNegativeDepth ?? true) && depth < 0) {
|
|
1231
|
+
i++;
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (readingKey) currentKey += c;
|
|
1238
|
+
else {
|
|
1239
|
+
currentValue += c;
|
|
1240
|
+
if (currentValuePos === -1 && depth === 0) currentValuePos = i;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
resultType !== "list" || currentValue.trim() !== "" ? commitPair() : ((currentKey = ""), (currentValue = ""), (currentValuePos = -1));
|
|
1245
|
+
|
|
1246
|
+
if (resultType === "list") {
|
|
1247
|
+
const baseVal = parseList(
|
|
1248
|
+
listResult.map(([v]: [value: string, pos: number]): string => v),
|
|
1249
|
+
{ ...options, isInnerStack: true }
|
|
1250
|
+
);
|
|
1251
|
+
if ("errors" in baseVal) {
|
|
1252
|
+
baseVal.errors.forEach((err) => {
|
|
1253
|
+
const lastStackItem = err.cause.stack.at(-1)! as Extract<SNBTParseErrorCauseStackItem, { function: "parseTypedArray" }>;
|
|
1254
|
+
// console.log(listResult, lastStackItem, lastStackItem.inputItemIndex, err);
|
|
1255
|
+
const baseOffset: number = listResult[lastStackItem.inputItemIndex]![1];
|
|
1256
|
+
err.cause.stack.push({
|
|
1257
|
+
input: originalInput,
|
|
1258
|
+
positionInInput: originalInput.indexOf(input) + baseOffset,
|
|
1259
|
+
function: "extractSNBT",
|
|
1260
|
+
});
|
|
1261
|
+
err.cause.position += originalInput.indexOf(input) + baseOffset;
|
|
1262
|
+
});
|
|
1263
|
+
errors.push(...baseVal.errors);
|
|
1264
|
+
}
|
|
1265
|
+
if (!options.isInnerStack) {
|
|
1266
|
+
errors.forEach((err: SNBTParseError): void => {
|
|
1267
|
+
err.isResolved = true;
|
|
1268
|
+
err.originalInput = originalInput;
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
value: "errors" in baseVal ? baseVal.value : baseVal,
|
|
1273
|
+
remaining: originalInput.slice(i + originalInput.indexOf(input)),
|
|
1274
|
+
startPos: originalInput.indexOf(input),
|
|
1275
|
+
endPos: i + originalInput.indexOf(input),
|
|
1276
|
+
...(options.keepGoingAfterError ? { errors } : undefined),
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (!options.isInnerStack) {
|
|
1281
|
+
errors.forEach((err) => {
|
|
1282
|
+
err.isResolved = true;
|
|
1283
|
+
err.originalInput = originalInput;
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return {
|
|
1288
|
+
value: NBT.comp(
|
|
1289
|
+
Object.fromEntries(
|
|
1290
|
+
Object.entries(result)
|
|
1291
|
+
.map(([k, v]: [string, any]): [string | undefined, any] => [
|
|
1292
|
+
((): string | undefined => {
|
|
1293
|
+
try {
|
|
1294
|
+
return parseFormattedKey(k.trim());
|
|
1295
|
+
} catch {
|
|
1296
|
+
const error = new SNBTParseError("Invalid SNBT key: " + k.trim(), {
|
|
1297
|
+
cause: {
|
|
1298
|
+
position: k.indexOf(k.trim()),
|
|
1299
|
+
stack: [
|
|
1300
|
+
{
|
|
1301
|
+
function: "parseSNBTCompoundString",
|
|
1302
|
+
input: k,
|
|
1303
|
+
positionInInput: k.indexOf(k.trim()),
|
|
1304
|
+
key: k.trim(),
|
|
1305
|
+
error: {
|
|
1306
|
+
type: "InvalidSNBTKey",
|
|
1307
|
+
raw: k.trim(),
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
],
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
if (options.keepGoingAfterError) errors.push(error);
|
|
1314
|
+
else throw error;
|
|
1315
|
+
return undefined;
|
|
1316
|
+
}
|
|
1317
|
+
})(),
|
|
1318
|
+
v,
|
|
1319
|
+
])
|
|
1320
|
+
.filter((v: [string | undefined, any]): v is [string, any] => v[0] !== undefined)
|
|
1321
|
+
)
|
|
1322
|
+
),
|
|
1323
|
+
remaining: originalInput.slice(i + originalInput.indexOf(input)),
|
|
1324
|
+
startPos: originalInput.indexOf(input),
|
|
1325
|
+
endPos: i + originalInput.indexOf(input),
|
|
1326
|
+
...(options.keepGoingAfterError ? { errors } : undefined),
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// console.log(
|
|
1331
|
+
// parseSNBTCompoundString(`{
|
|
1332
|
+
// id: SculkCatalyst,
|
|
1333
|
+
// isMovable: 1b,
|
|
1334
|
+
// x: -345,
|
|
1335
|
+
// y: -40,
|
|
1336
|
+
// z: 449
|
|
1337
|
+
// }`)
|
|
1338
|
+
// );
|
|
1339
|
+
|
|
1340
|
+
// console.log(
|
|
1341
|
+
// prismarineToSNBT(
|
|
1342
|
+
// parseSNBTCompoundString(`{
|
|
1343
|
+
// "id": SculkCatalyst,
|
|
1344
|
+
// "isMovable": 1b,
|
|
1345
|
+
// "x": -345,
|
|
1346
|
+
// "y": -40,
|
|
1347
|
+
// "z": 449
|
|
1348
|
+
// }`)
|
|
1349
|
+
// )
|
|
1350
|
+
// );
|
|
1351
|
+
|
|
1352
|
+
// console.log(
|
|
1353
|
+
// snbtToPrismarine(
|
|
1354
|
+
// prismarineToSNBT(
|
|
1355
|
+
// parseSNBTCompoundString(`{
|
|
1356
|
+
// "id": SculkCatalyst,
|
|
1357
|
+
// "isMovable": 1b,
|
|
1358
|
+
// "x": -345,
|
|
1359
|
+
// "y": -40,
|
|
1360
|
+
// "z": 449
|
|
1361
|
+
// }`)
|
|
1362
|
+
// )
|
|
1363
|
+
// )
|
|
1364
|
+
// );
|
|
1365
|
+
|
|
1366
|
+
// console.log(
|
|
1367
|
+
// prettyPrintSNBT(
|
|
1368
|
+
// prismarineToSNBT(
|
|
1369
|
+
// parseSNBTCompoundString(`{
|
|
1370
|
+
// "id": SculkCatalyst,
|
|
1371
|
+
// "isMovable": 1b,
|
|
1372
|
+
// "x": -345,
|
|
1373
|
+
// "y": -40,
|
|
1374
|
+
// "z": 449
|
|
1375
|
+
// }`)
|
|
1376
|
+
// ),
|
|
1377
|
+
// { indent: 4, inlineArrays: false }
|
|
1378
|
+
// )
|
|
1379
|
+
// );
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Converts SNBT-like JSON into Prismarine-NBT JSON.
|
|
1383
|
+
*
|
|
1384
|
+
* @param snbt The SNBT-like JSON to convert.
|
|
1385
|
+
* @returns The Prismarine-NBT JSON.
|
|
1386
|
+
*/
|
|
1387
|
+
export function snbtToPrismarine(snbt: any): Compound {
|
|
1388
|
+
const value: Record<string, NBT.Tags[NBT.TagType]> = {};
|
|
1389
|
+
|
|
1390
|
+
for (const [key, raw] of Object.entries(snbt)) {
|
|
1391
|
+
if (Array.isArray(raw)) {
|
|
1392
|
+
if (raw.length > 0 && typeof raw[0] === "object" && !Array.isArray(raw[0])) {
|
|
1393
|
+
value[key] = {
|
|
1394
|
+
type: "list",
|
|
1395
|
+
value: {
|
|
1396
|
+
type: "compound",
|
|
1397
|
+
value: raw.map((entry: any) => {
|
|
1398
|
+
const inner: Record<string, NBT.Tags[NBT.TagType]> = {};
|
|
1399
|
+
for (const [k, v] of Object.entries(entry)) {
|
|
1400
|
+
inner[k] = snbtToPrismarine({ [k]: v });
|
|
1401
|
+
}
|
|
1402
|
+
return inner;
|
|
1403
|
+
}),
|
|
1404
|
+
},
|
|
1405
|
+
};
|
|
1406
|
+
} else {
|
|
1407
|
+
const parsed: NBT.Tags[NBT.TagType][] = raw.map((v) => parseSNBTPrimitive(v));
|
|
1408
|
+
const types = new Set(parsed.map((p: NBT.Tags[NBT.TagType]): `${NBT.TagType}` => p.type));
|
|
1409
|
+
if (types.size === 1) {
|
|
1410
|
+
const type: `${NBT.TagType}` = [...types][0]!;
|
|
1411
|
+
switch (type) {
|
|
1412
|
+
case "byte":
|
|
1413
|
+
value[key] = NBT.byteArray(parsed.map((p: NBT.Tags[NBT.TagType]): number => p.value as number));
|
|
1414
|
+
continue;
|
|
1415
|
+
case "short":
|
|
1416
|
+
value[key] = NBT.shortArray(parsed.map((p: NBT.Tags[NBT.TagType]): number => p.value as number));
|
|
1417
|
+
continue;
|
|
1418
|
+
case "int":
|
|
1419
|
+
value[key] = NBT.intArray(parsed.map((p: NBT.Tags[NBT.TagType]): number => p.value as number));
|
|
1420
|
+
continue;
|
|
1421
|
+
case "long":
|
|
1422
|
+
value[key] = NBT.longArray(parsed.map((p: NBT.Tags[NBT.TagType]): [high: number, low: number] => p.value as [number, number]));
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
value[key] = {
|
|
1427
|
+
type: "list",
|
|
1428
|
+
value: {
|
|
1429
|
+
type: parsed[0]?.type ?? "int",
|
|
1430
|
+
value: parsed.map((p: NBT.Tags[NBT.TagType]): NBT.Tags[NBT.TagType]["value"] => p.value),
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
} else if (typeof raw === "object" && raw !== null) {
|
|
1435
|
+
if ("__typed" in raw && "value" in raw) {
|
|
1436
|
+
value[key] = parseTypedArray(
|
|
1437
|
+
raw.__typed as any,
|
|
1438
|
+
(raw.value as any[]).map((v: any): string => (typeof v === "string" ? v : String(v)))
|
|
1439
|
+
);
|
|
1440
|
+
} else {
|
|
1441
|
+
value[key] = snbtToPrismarine(raw);
|
|
1442
|
+
}
|
|
1443
|
+
} else {
|
|
1444
|
+
value[key] = parseSNBTPrimitive(raw);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
return NBT.comp(value);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function formatString(s: string): string {
|
|
1452
|
+
const unquotedPattern = /^[A-Za-z_][A-Za-z0-9_\-\+\.]*$/;
|
|
1453
|
+
if (unquotedPattern.test(s)) return s;
|
|
1454
|
+
|
|
1455
|
+
let quote: '"' | "'" = '"';
|
|
1456
|
+
if (!s.includes("'") && s.includes('"')) {
|
|
1457
|
+
quote = "'";
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const escaped: string = s
|
|
1461
|
+
.replace(/\\/g, "\\\\")
|
|
1462
|
+
.replace(new RegExp(quote, "g"), "\\" + quote)
|
|
1463
|
+
.replace(/\n/g, "\\n")
|
|
1464
|
+
.replace(/\r/g, "\\r")
|
|
1465
|
+
.replace(/\t/g, "\\t")
|
|
1466
|
+
.replace(/\x08/g, "\\b")
|
|
1467
|
+
.replace(/\f/g, "\\f");
|
|
1468
|
+
|
|
1469
|
+
return `${quote}${escaped}${quote}`;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function formatKey(key: string): string {
|
|
1473
|
+
const unquotedPattern = /^[A-Za-z0-9_\-\+\.]+$/;
|
|
1474
|
+
if (unquotedPattern.test(key)) return key;
|
|
1475
|
+
return formatString(key);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function parseFormattedString(string: string): string {
|
|
1479
|
+
return string.startsWith('"') && string.endsWith('"')
|
|
1480
|
+
? JSON.parse(string)
|
|
1481
|
+
: string.startsWith("'") && string.endsWith("'")
|
|
1482
|
+
? JSON.parse('"' + string.replaceAll('"', '\\"').slice(1, -1) + '"')
|
|
1483
|
+
: /^[A-Za-z_][A-Za-z0-9_\-\+\.]*$/.test(string)
|
|
1484
|
+
? string
|
|
1485
|
+
: ((): never => {
|
|
1486
|
+
throw new SyntaxError("Invalid SNBT string: " + string);
|
|
1487
|
+
})();
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function parseFormattedKey(key: string): string {
|
|
1491
|
+
return /^[A-Za-z0-9_\-\+\.]+$/.test(key)
|
|
1492
|
+
? key
|
|
1493
|
+
: key.startsWith('"') && key.endsWith('"')
|
|
1494
|
+
? JSON.parse(key)
|
|
1495
|
+
: key.startsWith("'") && key.endsWith("'")
|
|
1496
|
+
? JSON.parse('"' + key.replaceAll('"', '\\"').slice(1, -1) + '"')
|
|
1497
|
+
: /^[A-Za-z_][A-Za-z0-9_\-\+\.]*$/.test(key)
|
|
1498
|
+
? key
|
|
1499
|
+
: ((): never => {
|
|
1500
|
+
throw new SyntaxError("Invalid SNBT key: " + key);
|
|
1501
|
+
})();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
export enum SNBTLikeJSONTagType {
|
|
1505
|
+
Byte = "byte",
|
|
1506
|
+
Short = "short",
|
|
1507
|
+
Int = "int",
|
|
1508
|
+
Long = "long",
|
|
1509
|
+
Float = "float",
|
|
1510
|
+
Double = "double",
|
|
1511
|
+
String = "string",
|
|
1512
|
+
List = "list",
|
|
1513
|
+
Compound = "compound",
|
|
1514
|
+
ByteArray = "byteArray",
|
|
1515
|
+
ShortArray = "shortArray",
|
|
1516
|
+
IntArray = "intArray",
|
|
1517
|
+
LongArray = "longArray",
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
export type SNBTLikeJSONTags = {
|
|
1521
|
+
[SNBTLikeJSONTagType.Byte]: `${bigint}${"b" | "B"}`;
|
|
1522
|
+
[SNBTLikeJSONTagType.Short]: `${bigint}${"s" | "S"}`;
|
|
1523
|
+
[SNBTLikeJSONTagType.Int]: `${bigint}${"" | "i" | "I"}`;
|
|
1524
|
+
[SNBTLikeJSONTagType.Long]: `${bigint}${"l" | "L"}`;
|
|
1525
|
+
[SNBTLikeJSONTagType.Float]: `${number}${"f" | "F"}`;
|
|
1526
|
+
[SNBTLikeJSONTagType.Double]: `${number}${"d" | "D"}`;
|
|
1527
|
+
[SNBTLikeJSONTagType.String]: `"${string}"` | `'${string}'` | `${string}`;
|
|
1528
|
+
[SNBTLikeJSONTagType.List]: SNBTLikeJSONList<SNBTLikeJSONTagType>;
|
|
1529
|
+
[SNBTLikeJSONTagType.Compound]: SNBTLikeJSONCompound;
|
|
1530
|
+
[SNBTLikeJSONTagType.ByteArray]: SNBTLikeJSONTypedArray<TypedArrayLetterTypes.B>;
|
|
1531
|
+
[SNBTLikeJSONTagType.ShortArray]: SNBTLikeJSONTypedArray<TypedArrayLetterTypes.S>;
|
|
1532
|
+
[SNBTLikeJSONTagType.IntArray]: SNBTLikeJSONTypedArray<TypedArrayLetterTypes.I>;
|
|
1533
|
+
[SNBTLikeJSONTagType.LongArray]: SNBTLikeJSONTypedArray<TypedArrayLetterTypes.L>;
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
export type SNBTLikeJSONList<T extends SNBTLikeJSONTagType> = SNBTLikeJSONTags[T][];
|
|
1537
|
+
|
|
1538
|
+
export type SNBTLikeJSONTypedArray<T extends TypedArrayLetterTypes> = {
|
|
1539
|
+
__typed: keyof OmitNeverValueKeys<{ [key in keyof typeof TypedArrayLetterTypes]: (typeof TypedArrayLetterTypes)[key] extends T ? key : never }>;
|
|
1540
|
+
value: SNBTLikeJSONTags[VerifyConstraint<T, `${SNBTLikeJSONTagType}`>][];
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
export type SNBTLikeJSONCompound = { [key: string]: SNBTLikeJSONTag<SNBTLikeJSONTagType> };
|
|
1544
|
+
|
|
1545
|
+
export type SNBTLikeJSONTag<T extends `${SNBTLikeJSONTagType}` | SNBTLikeJSONTagType> = { [key in SNBTLikeJSONTagType as `${key}`]: SNBTLikeJSONTags[key] }[T];
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Converts Prismarine-NBT JSON to SNBT-like JSON.
|
|
1549
|
+
*
|
|
1550
|
+
* @param nbt The Prismarine-NBT JSON to convert.
|
|
1551
|
+
* @returns The resulting SNBT-like JSON.
|
|
1552
|
+
*/
|
|
1553
|
+
export function prismarineToSNBT<T extends NBT.Tags[NBT.TagType]>(nbt: T): SNBTLikeJSONTag<T["type"]> {
|
|
1554
|
+
if (nbt.type === "compound") {
|
|
1555
|
+
const obj: SNBTLikeJSONCompound = {};
|
|
1556
|
+
for (const [key, val] of Object.entries(nbt.value)) {
|
|
1557
|
+
obj[key] = prismarineToSNBT(val!);
|
|
1558
|
+
}
|
|
1559
|
+
return obj as any;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (nbt.type === "list") {
|
|
1563
|
+
if (nbt.value.type === "compound") {
|
|
1564
|
+
return nbt.value.value.map((entry: any): Record<string, any> => {
|
|
1565
|
+
const obj: Record<string, any> = {};
|
|
1566
|
+
for (const [k, v] of Object.entries(entry)) {
|
|
1567
|
+
obj[k] = prismarineToSNBT(v as any);
|
|
1568
|
+
}
|
|
1569
|
+
return obj;
|
|
1570
|
+
}) as any;
|
|
1571
|
+
} else {
|
|
1572
|
+
return nbt.value.value.map((v) => prismarineToSNBT({ type: nbt.value.type, value: v as any })) as any;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
switch (nbt.type) {
|
|
1577
|
+
case "byteArray":
|
|
1578
|
+
return { __typed: "B", value: nbt.value.map((v: number): `${number}b` => `${v}b` as const) } as any;
|
|
1579
|
+
case "shortArray":
|
|
1580
|
+
return { __typed: "S", value: nbt.value.map((v: number): `${number}s` => `${v}s` as const) } as any;
|
|
1581
|
+
case "intArray":
|
|
1582
|
+
return { __typed: "I", value: nbt.value.map((v: number): `${number}` => `${v}` as const) } as any;
|
|
1583
|
+
case "longArray":
|
|
1584
|
+
return { __typed: "L", value: nbt.value.map((v: [number, number]): `${bigint}L` => `${toLong(v)}L` as const) } as any;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
switch (nbt.type) {
|
|
1588
|
+
case "byte":
|
|
1589
|
+
return `${nbt.value}b` as const as any;
|
|
1590
|
+
case "short":
|
|
1591
|
+
return `${nbt.value}s` as const as any;
|
|
1592
|
+
case "int":
|
|
1593
|
+
return nbt.value as any;
|
|
1594
|
+
case "long":
|
|
1595
|
+
return `${toLong(nbt.value).toString()}L` as const as any;
|
|
1596
|
+
case "float":
|
|
1597
|
+
return `${nbt.value}f` as const as any;
|
|
1598
|
+
case "double":
|
|
1599
|
+
return `${nbt.value}d` as const as any;
|
|
1600
|
+
case "string":
|
|
1601
|
+
return formatString(nbt.value) as any;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
return (nbt as any).value;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* Converts a long in bigint format to tuple form.
|
|
1609
|
+
*
|
|
1610
|
+
* @param longVal The long as a bigint.
|
|
1611
|
+
* @returns The long as a tuple.
|
|
1612
|
+
*/
|
|
1613
|
+
export function toLongParts(longVal: bigint): [high: number, low: number] {
|
|
1614
|
+
const low: number = Number(longVal & 0xffffffffn) | 0;
|
|
1615
|
+
const high: number = Number((longVal >> 32n) & 0xffffffffn) | 0;
|
|
1616
|
+
return [high, low];
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Converts a long as a string to a tuple.
|
|
1621
|
+
*
|
|
1622
|
+
* @param literal A long as a string.
|
|
1623
|
+
* @returns The long as a tuple.
|
|
1624
|
+
*/
|
|
1625
|
+
export function parseLong(literal: string): [high: number, low: number] {
|
|
1626
|
+
const numStr: string = literal.slice(0, -1);
|
|
1627
|
+
const value: bigint = BigInt(numStr);
|
|
1628
|
+
return toLongParts(value);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Converts a long in tuple form to bigint form.
|
|
1633
|
+
*
|
|
1634
|
+
* @param param0 The long as a tuple.
|
|
1635
|
+
* @returns The long as a bigint.
|
|
1636
|
+
*/
|
|
1637
|
+
export function toLong([high, low]: [high: number, low: number]): bigint {
|
|
1638
|
+
const lo: bigint = BigInt(low) & BigInt(0xffffffff);
|
|
1639
|
+
const hi: bigint = BigInt(high);
|
|
1640
|
+
return (hi << 32n) | lo;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Options for {@link prettyPrintSNBT}.
|
|
1645
|
+
*/
|
|
1646
|
+
export interface PrettyPrintOptions {
|
|
1647
|
+
/**
|
|
1648
|
+
* Number of spaces to indent.
|
|
1649
|
+
*
|
|
1650
|
+
* @default 2
|
|
1651
|
+
*/
|
|
1652
|
+
indent?: number;
|
|
1653
|
+
/**
|
|
1654
|
+
* Whether to inline arrays if possible.
|
|
1655
|
+
*
|
|
1656
|
+
* @default true
|
|
1657
|
+
*/
|
|
1658
|
+
inlineArrays?: boolean;
|
|
1659
|
+
/**
|
|
1660
|
+
* Maximum length of an inline array.
|
|
1661
|
+
*
|
|
1662
|
+
* @default 40
|
|
1663
|
+
*/
|
|
1664
|
+
maxInlineLength?: number;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* Pretty-prints SNBT.
|
|
1669
|
+
*
|
|
1670
|
+
* @param obj The SNBT-like JSON to pretty-print.
|
|
1671
|
+
* @param options The options to use.
|
|
1672
|
+
* @returns The pretty-printed SNBT.
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```typescript
|
|
1676
|
+
* prettyPrintSNBT(prismarineToSNBT({
|
|
1677
|
+
* type: "compound",
|
|
1678
|
+
* value: {
|
|
1679
|
+
* enabled: {
|
|
1680
|
+
* type: "byte",
|
|
1681
|
+
* value: 1
|
|
1682
|
+
* }
|
|
1683
|
+
* }
|
|
1684
|
+
* }))
|
|
1685
|
+
* ```
|
|
1686
|
+
*/
|
|
1687
|
+
export function prettyPrintSNBT(obj: any, options: PrettyPrintOptions = {}): string {
|
|
1688
|
+
const { indent = 2, inlineArrays = true, maxInlineLength = 40 } = options;
|
|
1689
|
+
|
|
1690
|
+
function format(value: any, level: number): string {
|
|
1691
|
+
const pad: string = " ".repeat(level * indent);
|
|
1692
|
+
|
|
1693
|
+
if (value && typeof value === "object" && value.__typed) {
|
|
1694
|
+
const type: "B" | "S" | "I" | "L" = value.__typed;
|
|
1695
|
+
const inner: string = value.value.join(", ");
|
|
1696
|
+
return `[${type}; ${inner}]`;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
if (Array.isArray(value)) {
|
|
1700
|
+
if (value.length === 0) return "[]";
|
|
1701
|
+
const inline = `[${value.map((v: any): string => format(v, 0)).join(", ")}]`;
|
|
1702
|
+
if (inlineArrays && inline.length <= maxInlineLength && !value.some((v: any): boolean => typeof v === "object")) {
|
|
1703
|
+
return inline;
|
|
1704
|
+
}
|
|
1705
|
+
return "[\n" + value.map((v: any): string => pad + " ".repeat(indent) + format(v, level + 1)).join(",\n") + "\n" + pad + "]";
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
if (typeof value === "object" && value !== null) {
|
|
1709
|
+
const entries: [string, unknown][] = Object.entries(value);
|
|
1710
|
+
if (entries.length === 0) return "{}";
|
|
1711
|
+
|
|
1712
|
+
return (
|
|
1713
|
+
"{\n" +
|
|
1714
|
+
entries.map(([k, v]: [string, unknown]): string => pad + " ".repeat(indent) + `${formatKey(k)}: ${format(v, level + 1)}`).join(",\n") +
|
|
1715
|
+
"\n" +
|
|
1716
|
+
pad +
|
|
1717
|
+
"}"
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
return String(value);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
return format(obj, 0);
|
|
1725
|
+
}
|