s2cfgtojson 2.2.13 → 2.3.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/Struct.mts +280 -214
- package/Struct.test.mts +81 -37
- package/enums.mts +21 -6
- package/package.json +5 -4
- package/test.cfg +598 -0
- package/types.mts +17 -12
package/Struct.mts
CHANGED
|
@@ -1,262 +1,328 @@
|
|
|
1
|
-
const defaultEntries = new Set(["_isArray", "_useAsterisk"]);
|
|
2
1
|
export * from "./types.mts";
|
|
3
2
|
export * from "./enums.mts";
|
|
4
|
-
import { DefaultEntries,
|
|
3
|
+
import { DefaultEntries, Struct as IStruct, Value } from "./types.mts";
|
|
4
|
+
|
|
5
|
+
const TAB = " ";
|
|
6
|
+
const WILDCARD = "_wildcard";
|
|
7
|
+
const KEYWORDS = [
|
|
8
|
+
"refurl", // a file path to override
|
|
9
|
+
"refkey", // SID to override
|
|
10
|
+
"bskipref", // ??? not sure
|
|
11
|
+
"bpatch", // allows patching only specific keys
|
|
12
|
+
];
|
|
13
|
+
const REMOVE_NODE = "removenode";
|
|
5
14
|
|
|
6
15
|
/**
|
|
7
16
|
* This file is part of the Stalker 2 Modding Tools project.
|
|
8
17
|
* This is a base class for all structs.
|
|
9
18
|
*/
|
|
10
|
-
export abstract class Struct
|
|
11
|
-
|
|
12
|
-
refurl?: string;
|
|
13
|
-
refkey?: string | number;
|
|
14
|
-
bskipref?: boolean;
|
|
15
|
-
abstract _id: string;
|
|
16
|
-
entries: T;
|
|
19
|
+
export abstract class Struct implements IStruct {
|
|
20
|
+
__internal__: Refs = new Refs();
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return Number.isInteger(parseInt(ref)) || typeof ref === "number";
|
|
25
|
-
}
|
|
26
|
-
static isArrayKey(key: string) {
|
|
27
|
-
return key.includes("[") && key.includes("]");
|
|
28
|
-
}
|
|
29
|
-
static renderKeyName(ref: string, useAsterisk?: boolean): string {
|
|
30
|
-
if (`${ref}`.startsWith("_")) {
|
|
31
|
-
return Struct.renderKeyName(ref.slice(1), useAsterisk); // Special case for indexed structs
|
|
32
|
-
}
|
|
33
|
-
if (`${ref}`.includes("*") || useAsterisk) {
|
|
34
|
-
return "[*]"; // Special case for wildcard structs
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new struct instance.
|
|
24
|
+
*/
|
|
25
|
+
constructor(parentOrRaw?: string | Struct) {
|
|
26
|
+
if (parentOrRaw instanceof Struct) {
|
|
27
|
+
Object.assign(this, parentOrRaw.clone());
|
|
35
28
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
29
|
+
if (typeof parentOrRaw === "string") {
|
|
30
|
+
Object.assign(this, Struct.fromString(parentOrRaw)[0]);
|
|
38
31
|
}
|
|
39
|
-
if (Struct.isNumber(ref)) {
|
|
40
|
-
return `[${parseInt(ref)}]`;
|
|
41
|
-
}
|
|
42
|
-
return ref;
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
if (Struct.isNumber(name)) {
|
|
53
|
-
return `[${parseInt(name)}]`;
|
|
54
|
-
}
|
|
55
|
-
return name;
|
|
56
|
-
}
|
|
34
|
+
fork(clone = false) {
|
|
35
|
+
const patch = clone
|
|
36
|
+
? this.clone()
|
|
37
|
+
: createDynamicClassInstance(
|
|
38
|
+
this.__internal__.rawName || this.constructor.name,
|
|
39
|
+
);
|
|
57
40
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
function markAsbPatch(s: Struct) {
|
|
42
|
+
s.__internal__.bpatch = true;
|
|
43
|
+
Object.values(s)
|
|
44
|
+
.filter((v) => v instanceof Struct)
|
|
45
|
+
.forEach(markAsbPatch);
|
|
61
46
|
}
|
|
62
|
-
|
|
47
|
+
|
|
48
|
+
markAsbPatch(patch);
|
|
49
|
+
return patch as this;
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return `_${name.match(/\[(\d+)]/)[1]}`; // Special case for indexed structs
|
|
52
|
+
removeNode(key: keyof this) {
|
|
53
|
+
if (this.__internal__.bpatch !== true) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Cannot remove node from non-patch struct. Use fork() first.",
|
|
56
|
+
);
|
|
71
57
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.replace(/^\d+/, "_")
|
|
75
|
-
.replace(/_+/g, "_")
|
|
76
|
-
.replace(/^_+/, "");
|
|
58
|
+
this[key] = REMOVE_NODE as any;
|
|
59
|
+
return this;
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
|
|
80
|
-
|
|
62
|
+
clone() {
|
|
63
|
+
return Struct.fromString(this.toString())[0] as this;
|
|
64
|
+
}
|
|
81
65
|
|
|
82
66
|
toString(): string {
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
if (!(this.__internal__ instanceof Refs)) {
|
|
68
|
+
this.__internal__ = new Refs(this.__internal__);
|
|
69
|
+
}
|
|
70
|
+
const { __internal__: internal, ...entries } = this;
|
|
71
|
+
|
|
72
|
+
let text: string = internal.rawName ? `${internal.rawName} : ` : "";
|
|
85
73
|
text += "struct.begin";
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.filter(([_, v]) => v !== "" && v !== undefined && v !== false)
|
|
89
|
-
.map(([k, v]) => {
|
|
90
|
-
if (v === true) return k;
|
|
91
|
-
return `${k}=${Struct.renderKeyName(v)}`;
|
|
92
|
-
})
|
|
93
|
-
.join(";");
|
|
74
|
+
|
|
75
|
+
const refs = internal.toString();
|
|
94
76
|
if (refs) {
|
|
95
77
|
text += ` {${refs}}`;
|
|
96
78
|
}
|
|
79
|
+
|
|
97
80
|
text += "\n";
|
|
98
81
|
// Add all keys
|
|
99
|
-
text += Object.entries(
|
|
100
|
-
.filter(([key]) => key !== "_useAsterisk" && key !== "_isArray")
|
|
82
|
+
text += Object.entries(entries || {})
|
|
101
83
|
.map(([key, value]) => {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
84
|
+
const nameAlreadyRendered =
|
|
85
|
+
value instanceof Struct && value.__internal__.rawName;
|
|
86
|
+
const useAsterisk = internal.isArray && internal.useAsterisk;
|
|
87
|
+
let keyOrIndex = "";
|
|
88
|
+
let equalsOrColon = "";
|
|
89
|
+
let spaceOrNoSpace = "";
|
|
90
|
+
if (!nameAlreadyRendered) {
|
|
91
|
+
keyOrIndex = renderKeyName(key, useAsterisk) + " ";
|
|
92
|
+
equalsOrColon = value instanceof Struct ? ":" : "=";
|
|
93
|
+
spaceOrNoSpace = value === "" ? "" : " ";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return pad(`${keyOrIndex}${equalsOrColon}${spaceOrNoSpace}${value}`);
|
|
108
97
|
})
|
|
109
98
|
.join("\n");
|
|
110
99
|
text += "\nstruct.end";
|
|
111
100
|
return text;
|
|
112
101
|
}
|
|
113
102
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Object.entries(struct.entries)
|
|
119
|
-
.filter(([key]) => !defaultEntries.has(key))
|
|
120
|
-
.forEach(([key, value]) => {
|
|
121
|
-
if (value instanceof Struct) {
|
|
122
|
-
obj[key] = collect(value);
|
|
123
|
-
} else {
|
|
124
|
-
obj[key] = value;
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
return obj;
|
|
129
|
-
};
|
|
130
|
-
return JSON.stringify(collect(this), null, pretty ? 2 : 0);
|
|
103
|
+
static fromString<IntendedType extends Partial<Struct> = Struct>(
|
|
104
|
+
text: string,
|
|
105
|
+
): IntendedType[] {
|
|
106
|
+
return walk(text.trim().split("\n")) as IntendedType[];
|
|
131
107
|
}
|
|
108
|
+
}
|
|
132
109
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
export class Refs implements DefaultEntries {
|
|
111
|
+
rawName?: string;
|
|
112
|
+
refurl?: string;
|
|
113
|
+
refkey?: string | number;
|
|
114
|
+
bskipref?: boolean;
|
|
115
|
+
bpatch?: boolean;
|
|
116
|
+
isArray?: boolean;
|
|
117
|
+
useAsterisk?: boolean;
|
|
118
|
+
|
|
119
|
+
constructor(ref?: string | object) {
|
|
120
|
+
if (typeof ref === "string") {
|
|
121
|
+
ref
|
|
122
|
+
.split(";")
|
|
123
|
+
.map((ref) => ref.trim())
|
|
124
|
+
.filter(Boolean)
|
|
125
|
+
.reduce((acc, ref) => {
|
|
126
|
+
const [key, value] = ref.split("=");
|
|
127
|
+
if (KEYWORDS.includes(key.trim())) {
|
|
128
|
+
acc[key.trim()] = value ? value.trim() : true;
|
|
129
|
+
}
|
|
130
|
+
return acc;
|
|
131
|
+
}, this);
|
|
132
|
+
}
|
|
133
|
+
if (typeof ref === "object") {
|
|
134
|
+
Object.assign(this, ref);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
151
137
|
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
toString() {
|
|
139
|
+
return KEYWORDS.map((k) => [k, this[k]])
|
|
140
|
+
.filter(([_, v]) => v !== "" && v !== undefined && v !== false)
|
|
141
|
+
.map(([k, v]) => {
|
|
142
|
+
if (v === true) return k;
|
|
143
|
+
if (k === "refkey") {
|
|
144
|
+
return `${k}=${renderKeyName(`${v}`, this.useAsterisk)}`;
|
|
154
145
|
}
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return normKey;
|
|
161
|
-
};
|
|
146
|
+
return `${k}=${v}`;
|
|
147
|
+
})
|
|
148
|
+
.join(";");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
162
151
|
|
|
163
|
-
|
|
152
|
+
const structHeadRegex = new RegExp(
|
|
153
|
+
`^(.*)\\s*:\\s*struct\\.begin\\s*({\\s*((${KEYWORDS.join("|")})\\s*(=.+)?)\\s*})?`,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
function parseHead(line: string, index: number): Struct {
|
|
157
|
+
const match = line.match(structHeadRegex);
|
|
158
|
+
if (!match) {
|
|
159
|
+
throw new Error(`Invalid struct head: ${line}`);
|
|
164
160
|
}
|
|
165
161
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
const dummy = createDynamicClassInstance(match[1].trim(), index);
|
|
163
|
+
if (match[3]) {
|
|
164
|
+
Object.assign(dummy.__internal__, new Refs(match[3]));
|
|
165
|
+
}
|
|
170
166
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
/^(.*)\s*:\s*struct\.begin\s*({\s*((refurl|refkey|bskipref)\s*(=.+)?)\s*})?/,
|
|
174
|
-
);
|
|
175
|
-
if (!match) {
|
|
176
|
-
throw new Error(`Invalid struct head: ${line}`);
|
|
177
|
-
}
|
|
178
|
-
let name =
|
|
179
|
-
Struct.parseStructName(match[1].trim()) || `UnnamedStruct${index}`;
|
|
180
|
-
|
|
181
|
-
const dummy = new (Struct.createDynamicClass(name))();
|
|
182
|
-
dummy._id = match[1].trim();
|
|
183
|
-
if (match[3]) {
|
|
184
|
-
const refs = match[3]
|
|
185
|
-
.split(";")
|
|
186
|
-
.map((ref) => ref.trim())
|
|
187
|
-
.filter(Boolean)
|
|
188
|
-
.reduce(
|
|
189
|
-
(acc, ref) => {
|
|
190
|
-
const [key, value] = ref.split("=");
|
|
191
|
-
acc[key.trim()] = value ? value.trim() : true;
|
|
192
|
-
return acc;
|
|
193
|
-
},
|
|
194
|
-
{} as { refurl?: string; refkey?: string; bskipref?: boolean },
|
|
195
|
-
);
|
|
196
|
-
if (refs.refurl) dummy.refurl = refs.refurl;
|
|
197
|
-
if (refs.refkey) dummy.refkey = refs.refkey;
|
|
198
|
-
if (refs.bskipref) dummy.bskipref = refs.bskipref;
|
|
199
|
-
}
|
|
200
|
-
return dummy as Struct;
|
|
201
|
-
};
|
|
167
|
+
return dummy as Struct;
|
|
168
|
+
}
|
|
202
169
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
170
|
+
export function pad(text: string): string {
|
|
171
|
+
return `${TAB}${text.replace(/\n+/g, `\n${TAB}`)}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isNumber(ref: string): boolean {
|
|
175
|
+
return Number.isInteger(parseInt(ref)) || typeof ref === "number";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function createDynamicClassInstance<T extends Struct = Struct>(
|
|
179
|
+
rawName: string,
|
|
180
|
+
index?: number,
|
|
181
|
+
): T {
|
|
182
|
+
const name = parseStructName(rawName) || `UnnamedStruct${index}`;
|
|
183
|
+
return new (new Function(
|
|
184
|
+
"parent",
|
|
185
|
+
"Refs",
|
|
186
|
+
`return class ${name} extends parent {
|
|
187
|
+
__internal__ = new Refs({ rawName: "${rawName.trim()}" });
|
|
188
|
+
}`,
|
|
189
|
+
)(Struct, Refs))() as T;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseKeyValue(line: string, parent: Struct, index: number): void {
|
|
193
|
+
const match = line.match(/^(.*?)(\s*:\s*|\s*=\s*)(.*)$/);
|
|
194
|
+
if (!match) {
|
|
195
|
+
throw new Error(`Invalid key-value pair: ${line}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const key = parseKey(match[1].trim(), parent, index);
|
|
199
|
+
|
|
200
|
+
parent[key] = parseValue(match[3].trim());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function walk(lines: string[]) {
|
|
204
|
+
const roots: Struct[] = [];
|
|
205
|
+
const stack = [];
|
|
206
|
+
let index = 0;
|
|
207
|
+
while (index < lines.length) {
|
|
208
|
+
const line = lines[index++].trim();
|
|
209
|
+
if (line.startsWith("#") || line.startsWith("//")) {
|
|
210
|
+
continue; // Skip comments
|
|
211
|
+
}
|
|
212
|
+
const current = stack[stack.length - 1];
|
|
213
|
+
|
|
214
|
+
if (line.includes("struct.begin")) {
|
|
215
|
+
const newStruct = parseHead(line, index);
|
|
216
|
+
if (current) {
|
|
217
|
+
const key = parseKey(
|
|
218
|
+
renderStructName(newStruct.constructor.name),
|
|
219
|
+
current,
|
|
220
|
+
index,
|
|
221
|
+
);
|
|
222
|
+
current[key] = newStruct;
|
|
212
223
|
} else {
|
|
213
|
-
|
|
214
|
-
// understand +- 0.1f / 1. / 0.f / .1 / .1f -> ((\d*)\.?(\d+)|(\d+)\.?(\d*))f?
|
|
215
|
-
const matches = value.match(/^(-?)(\d*)\.?(\d*)f?$/);
|
|
216
|
-
const minus = matches[1];
|
|
217
|
-
const first = matches[2];
|
|
218
|
-
const second = matches[3];
|
|
219
|
-
if (first || second) {
|
|
220
|
-
value = parseFloat(
|
|
221
|
-
`${minus ? "-" : ""}${first || 0}${second ? `.${second}` : ""}`,
|
|
222
|
-
);
|
|
223
|
-
} else {
|
|
224
|
-
value = JSON.parse(value);
|
|
225
|
-
}
|
|
226
|
-
} catch (e) {}
|
|
227
|
-
}
|
|
228
|
-
Struct.addEntry(parent, key, value, index);
|
|
229
|
-
};
|
|
230
|
-
let index = 0;
|
|
231
|
-
|
|
232
|
-
const walk = () => {
|
|
233
|
-
const roots: Struct[] = [];
|
|
234
|
-
const stack = [];
|
|
235
|
-
while (index < lines.length) {
|
|
236
|
-
const line = lines[index++].trim();
|
|
237
|
-
if (line.startsWith("#") || line.startsWith("//")) {
|
|
238
|
-
continue; // Skip comments
|
|
239
|
-
}
|
|
240
|
-
const current = stack[stack.length - 1];
|
|
241
|
-
if (line.includes("struct.begin")) {
|
|
242
|
-
const newStruct = parseHead(line, index);
|
|
243
|
-
if (current) {
|
|
244
|
-
const key = Struct.renderStructName(newStruct.constructor.name);
|
|
245
|
-
Struct.addEntry(current, key, newStruct, index);
|
|
246
|
-
} else {
|
|
247
|
-
newStruct.isRoot = true;
|
|
248
|
-
roots.push(newStruct);
|
|
249
|
-
}
|
|
250
|
-
stack.push(newStruct);
|
|
251
|
-
} else if (line.includes("struct.end")) {
|
|
252
|
-
stack.pop();
|
|
253
|
-
} else if (line.includes("=") && current) {
|
|
254
|
-
parseKeyValue(line, current);
|
|
255
|
-
}
|
|
224
|
+
roots.push(newStruct);
|
|
256
225
|
}
|
|
257
|
-
|
|
258
|
-
}
|
|
226
|
+
stack.push(newStruct);
|
|
227
|
+
} else if (line.includes("struct.end")) {
|
|
228
|
+
stack.pop();
|
|
229
|
+
} else if (line.includes("=") && current) {
|
|
230
|
+
parseKeyValue(line, current, index);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return roots;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseKey(key: string, parent: Struct, index: number) {
|
|
237
|
+
let normKey: string | number = key;
|
|
238
|
+
|
|
239
|
+
if (key.startsWith("[") && key.endsWith("]")) {
|
|
240
|
+
parent.__internal__.isArray = true;
|
|
241
|
+
normKey = extractKeyFromBrackets(key);
|
|
259
242
|
|
|
260
|
-
|
|
243
|
+
if (normKey === "*") {
|
|
244
|
+
parent.__internal__.useAsterisk = true;
|
|
245
|
+
return Object.keys(parent).length - 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (parent[normKey] !== undefined) {
|
|
249
|
+
return `${normKey}_dupe_${index}`;
|
|
250
|
+
}
|
|
251
|
+
return normKey;
|
|
252
|
+
}
|
|
253
|
+
if (parent[normKey] !== undefined) {
|
|
254
|
+
return `${normKey}_dupe_${index}`;
|
|
255
|
+
}
|
|
256
|
+
return normKey;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseValue(value: string): Value {
|
|
260
|
+
if (value === "true" || value === "false") {
|
|
261
|
+
return value === "true";
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
// understand +- 0.1f / 1. / 0.f / .1 / .1f -> ((\d*)\.?(\d+)|(\d+)\.?(\d*))f?
|
|
265
|
+
const matches = value.match(/^(-?)(\d*)\.?(\d*)f?$/);
|
|
266
|
+
const minus = matches[1];
|
|
267
|
+
const first = matches[2];
|
|
268
|
+
const second = matches[3];
|
|
269
|
+
if (first || second) {
|
|
270
|
+
return parseFloat(
|
|
271
|
+
`${minus ? "-" : ""}${first || 0}${second ? `.${second}` : ""}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return JSON.parse(value);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function renderStructName(name: string): string {
|
|
281
|
+
if (name === WILDCARD) {
|
|
282
|
+
return "[*]"; // Special case for wildcard structs
|
|
283
|
+
}
|
|
284
|
+
if (`${name}`.startsWith("_")) {
|
|
285
|
+
return renderStructName(name.slice(1)); // Special case for indexed structs
|
|
286
|
+
}
|
|
287
|
+
if (isNumber(name)) {
|
|
288
|
+
return `[${parseInt(name)}]`;
|
|
289
|
+
}
|
|
290
|
+
return name;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderKeyName(key: string, useAsterisk?: boolean): string {
|
|
294
|
+
if (`${key}`.startsWith("_")) {
|
|
295
|
+
return renderKeyName(key.slice(1), useAsterisk); // Special case for indexed structs
|
|
296
|
+
}
|
|
297
|
+
if (`${key}`.includes("*") || useAsterisk) {
|
|
298
|
+
return "[*]"; // Special case for wildcard structs
|
|
299
|
+
}
|
|
300
|
+
if (`${key}`.includes("_dupe_")) {
|
|
301
|
+
return renderKeyName(key.slice(0, key.indexOf("_dupe_")));
|
|
302
|
+
}
|
|
303
|
+
if (isNumber(key)) {
|
|
304
|
+
return `[${parseInt(key)}]`;
|
|
305
|
+
}
|
|
306
|
+
return key;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function extractKeyFromBrackets(key: string) {
|
|
310
|
+
if (/\[(.+)]/.test(key)) {
|
|
311
|
+
return key.match(/\[(.+)]/)[1];
|
|
312
|
+
}
|
|
313
|
+
return "";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function parseStructName(name: string): string {
|
|
317
|
+
if (extractKeyFromBrackets(name) === "*") {
|
|
318
|
+
return WILDCARD; // Special case for wildcard structs
|
|
319
|
+
}
|
|
320
|
+
if (isNumber(extractKeyFromBrackets(name))) {
|
|
321
|
+
return `_${name.match(/\[(\d+)]/)[1]}`; // Special case for indexed structs
|
|
261
322
|
}
|
|
323
|
+
return name
|
|
324
|
+
.replace(/\W/g, "_")
|
|
325
|
+
.replace(/^\d+/, "_")
|
|
326
|
+
.replace(/_+/g, "_")
|
|
327
|
+
.replace(/^_+/, "");
|
|
262
328
|
}
|