@k67/kaitai-struct-ts 0.3.0 → 0.6.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/README.md +56 -49
- package/dist/index.d.mts +276 -202
- package/dist/index.d.ts +276 -202
- package/dist/index.js +276 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +276 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
@@ -193,6 +193,7 @@ var KaitaiStream = class _KaitaiStream {
|
|
193
193
|
if (buffer instanceof ArrayBuffer) {
|
194
194
|
this.buffer = new Uint8Array(buffer);
|
195
195
|
this.view = new DataView(buffer);
|
196
|
+
this._size = buffer.byteLength;
|
196
197
|
} else {
|
197
198
|
this.buffer = buffer;
|
198
199
|
this.view = new DataView(
|
@@ -200,6 +201,7 @@ var KaitaiStream = class _KaitaiStream {
|
|
200
201
|
buffer.byteOffset,
|
201
202
|
buffer.byteLength
|
202
203
|
);
|
204
|
+
this._size = buffer.byteLength;
|
203
205
|
}
|
204
206
|
}
|
205
207
|
/**
|
@@ -216,13 +218,13 @@ var KaitaiStream = class _KaitaiStream {
|
|
216
218
|
* Total size of the stream in bytes
|
217
219
|
*/
|
218
220
|
get size() {
|
219
|
-
return this.
|
221
|
+
return this._size;
|
220
222
|
}
|
221
223
|
/**
|
222
224
|
* Check if we've reached the end of the stream
|
223
225
|
*/
|
224
226
|
isEof() {
|
225
|
-
return this._pos >= this.
|
227
|
+
return this._pos >= this._size;
|
226
228
|
}
|
227
229
|
/**
|
228
230
|
* Seek to a specific position in the stream
|
@@ -642,6 +644,7 @@ var KsyParser = class {
|
|
642
644
|
throw new ParseError("KSY file must contain an object");
|
643
645
|
}
|
644
646
|
const schema = parsed;
|
647
|
+
this.processParametricTypes(schema);
|
645
648
|
if (validate) {
|
646
649
|
const result = this.validate(schema, { strict });
|
647
650
|
if (!result.valid) {
|
@@ -659,6 +662,65 @@ var KsyParser = class {
|
|
659
662
|
}
|
660
663
|
return schema;
|
661
664
|
}
|
665
|
+
/**
|
666
|
+
* Process parametric type syntax in schema.
|
667
|
+
* Converts "type_name(arg1, arg2)" to structured format.
|
668
|
+
*
|
669
|
+
* @param schema - Schema to process
|
670
|
+
* @private
|
671
|
+
*/
|
672
|
+
processParametricTypes(schema) {
|
673
|
+
if (schema.seq) {
|
674
|
+
for (const attr of schema.seq) {
|
675
|
+
if (typeof attr.type === "string") {
|
676
|
+
this.parseParametricType(attr);
|
677
|
+
}
|
678
|
+
}
|
679
|
+
}
|
680
|
+
if (schema.instances) {
|
681
|
+
for (const instance of Object.values(schema.instances)) {
|
682
|
+
if (typeof instance.type === "string") {
|
683
|
+
this.parseParametricType(instance);
|
684
|
+
}
|
685
|
+
}
|
686
|
+
}
|
687
|
+
if (schema.types) {
|
688
|
+
for (const type of Object.values(schema.types)) {
|
689
|
+
this.processParametricTypes(type);
|
690
|
+
}
|
691
|
+
}
|
692
|
+
}
|
693
|
+
/**
|
694
|
+
* Parse parametric type syntax from a type string.
|
695
|
+
* Converts "type_name(arg1, arg2)" to type + type-args.
|
696
|
+
*
|
697
|
+
* @param attr - Attribute to process
|
698
|
+
* @private
|
699
|
+
*/
|
700
|
+
parseParametricType(attr) {
|
701
|
+
if (typeof attr.type !== "string") return;
|
702
|
+
const match = attr.type.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
|
703
|
+
if (!match) return;
|
704
|
+
const [, typeName, argsStr] = match;
|
705
|
+
const args = [];
|
706
|
+
if (argsStr.trim()) {
|
707
|
+
const argParts = argsStr.split(",").map((s) => s.trim());
|
708
|
+
for (const arg of argParts) {
|
709
|
+
const num = Number(arg);
|
710
|
+
if (!isNaN(num)) {
|
711
|
+
args.push(num);
|
712
|
+
} else if (arg === "true") {
|
713
|
+
args.push(true);
|
714
|
+
} else if (arg === "false") {
|
715
|
+
args.push(false);
|
716
|
+
} else {
|
717
|
+
args.push(arg);
|
718
|
+
}
|
719
|
+
}
|
720
|
+
}
|
721
|
+
attr.type = typeName;
|
722
|
+
attr["type-args"] = args;
|
723
|
+
}
|
662
724
|
/**
|
663
725
|
* Validate a schema object.
|
664
726
|
*
|
@@ -1857,9 +1919,7 @@ var Evaluator = class {
|
|
1857
1919
|
evaluateEnumAccess(enumName, valueName, context) {
|
1858
1920
|
const value = context.getEnumValue(enumName, valueName);
|
1859
1921
|
if (value === void 0) {
|
1860
|
-
throw new ParseError(
|
1861
|
-
`Enum value "${enumName}::${valueName}" not found`
|
1862
|
-
);
|
1922
|
+
throw new ParseError(`Enum value "${enumName}::${valueName}" not found`);
|
1863
1923
|
}
|
1864
1924
|
return value;
|
1865
1925
|
}
|
@@ -1956,6 +2016,10 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1956
2016
|
constructor(schema, parentMeta) {
|
1957
2017
|
this.schema = schema;
|
1958
2018
|
this.parentMeta = parentMeta;
|
2019
|
+
// Performance optimization: cache for constant expressions
|
2020
|
+
// Limited size to prevent memory leaks
|
2021
|
+
this.expressionCache = /* @__PURE__ */ new Map();
|
2022
|
+
this.MAX_CACHE_SIZE = 1e3;
|
1959
2023
|
if (!schema.meta && !parentMeta) {
|
1960
2024
|
throw new ParseError("Schema must have meta section");
|
1961
2025
|
}
|
@@ -1968,12 +2032,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1968
2032
|
*
|
1969
2033
|
* @param stream - Binary stream to parse
|
1970
2034
|
* @param parent - Parent object (for nested types)
|
2035
|
+
* @param typeArgs - Arguments for parametric types
|
1971
2036
|
* @returns Parsed object
|
1972
2037
|
*/
|
1973
|
-
parse(stream, parent) {
|
2038
|
+
parse(stream, parent, typeArgs) {
|
1974
2039
|
const result = {};
|
1975
2040
|
const context = new Context(stream, result, parent, this.schema.enums);
|
1976
2041
|
context.current = result;
|
2042
|
+
if (typeArgs && this.schema.params) {
|
2043
|
+
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
2044
|
+
const param = this.schema.params[i];
|
2045
|
+
const argValue = typeArgs[i];
|
2046
|
+
const evaluatedArg = typeof argValue === "string" ? this.evaluateValue(argValue, context) : argValue;
|
2047
|
+
context.set(param.id, evaluatedArg);
|
2048
|
+
}
|
2049
|
+
}
|
1977
2050
|
if (this.schema.seq) {
|
1978
2051
|
for (const attr of this.schema.seq) {
|
1979
2052
|
const value = this.parseAttribute(attr, context);
|
@@ -1982,8 +2055,86 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1982
2055
|
}
|
1983
2056
|
}
|
1984
2057
|
}
|
2058
|
+
if (this.schema.instances) {
|
2059
|
+
this.setupInstances(result, stream, context);
|
2060
|
+
}
|
1985
2061
|
return result;
|
1986
2062
|
}
|
2063
|
+
/**
|
2064
|
+
* Set up lazy-evaluated instance getters.
|
2065
|
+
* Instances are computed on first access and cached.
|
2066
|
+
*
|
2067
|
+
* @param result - Result object to add getters to
|
2068
|
+
* @param stream - Stream for parsing
|
2069
|
+
* @param context - Execution context
|
2070
|
+
* @private
|
2071
|
+
*/
|
2072
|
+
setupInstances(result, stream, context) {
|
2073
|
+
if (!this.schema.instances) return;
|
2074
|
+
for (const [name, instance] of Object.entries(this.schema.instances)) {
|
2075
|
+
let cached = void 0;
|
2076
|
+
let evaluated = false;
|
2077
|
+
Object.defineProperty(result, name, {
|
2078
|
+
get: () => {
|
2079
|
+
if (!evaluated) {
|
2080
|
+
cached = this.parseInstance(
|
2081
|
+
instance,
|
2082
|
+
stream,
|
2083
|
+
context
|
2084
|
+
);
|
2085
|
+
evaluated = true;
|
2086
|
+
}
|
2087
|
+
return cached;
|
2088
|
+
},
|
2089
|
+
enumerable: true,
|
2090
|
+
configurable: true
|
2091
|
+
});
|
2092
|
+
}
|
2093
|
+
}
|
2094
|
+
/**
|
2095
|
+
* Parse an instance (lazy-evaluated field).
|
2096
|
+
*
|
2097
|
+
* @param instance - Instance specification
|
2098
|
+
* @param stream - Stream to read from
|
2099
|
+
* @param context - Execution context
|
2100
|
+
* @returns Parsed or calculated value
|
2101
|
+
* @private
|
2102
|
+
*/
|
2103
|
+
parseInstance(instance, stream, context) {
|
2104
|
+
if ("value" in instance) {
|
2105
|
+
return this.evaluateValue(
|
2106
|
+
instance.value,
|
2107
|
+
context
|
2108
|
+
);
|
2109
|
+
}
|
2110
|
+
const savedPos = stream.pos;
|
2111
|
+
try {
|
2112
|
+
if (instance.pos !== void 0) {
|
2113
|
+
const pos = this.evaluateValue(
|
2114
|
+
instance.pos,
|
2115
|
+
context
|
2116
|
+
);
|
2117
|
+
if (typeof pos === "number") {
|
2118
|
+
stream.seek(pos);
|
2119
|
+
} else if (typeof pos === "bigint") {
|
2120
|
+
stream.seek(Number(pos));
|
2121
|
+
} else {
|
2122
|
+
throw new ParseError(
|
2123
|
+
`pos must evaluate to a number, got ${typeof pos}`
|
2124
|
+
);
|
2125
|
+
}
|
2126
|
+
}
|
2127
|
+
const value = this.parseAttribute(
|
2128
|
+
instance,
|
2129
|
+
context
|
2130
|
+
);
|
2131
|
+
return value;
|
2132
|
+
} finally {
|
2133
|
+
if (instance.pos !== void 0) {
|
2134
|
+
stream.seek(savedPos);
|
2135
|
+
}
|
2136
|
+
}
|
2137
|
+
}
|
1987
2138
|
/**
|
1988
2139
|
* Parse a single attribute according to its specification.
|
1989
2140
|
*
|
@@ -2013,6 +2164,11 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2013
2164
|
if (attr.io) {
|
2014
2165
|
throw new NotImplementedError("Custom I/O streams");
|
2015
2166
|
}
|
2167
|
+
if (!attr.type && !attr.size && !attr["size-eos"] && !attr.contents) {
|
2168
|
+
throw new ParseError(
|
2169
|
+
`Attribute "${attr.id || "unknown"}" must have type, size, size-eos, or contents`
|
2170
|
+
);
|
2171
|
+
}
|
2016
2172
|
if (attr.repeat) {
|
2017
2173
|
return this.parseRepeated(attr, context);
|
2018
2174
|
}
|
@@ -2062,7 +2218,13 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2062
2218
|
throw new ParseError("repeat-until expression is required");
|
2063
2219
|
}
|
2064
2220
|
let index = 0;
|
2221
|
+
const maxIterations = 1e6;
|
2065
2222
|
while (true) {
|
2223
|
+
if (index >= maxIterations) {
|
2224
|
+
throw new ParseError(
|
2225
|
+
`repeat-until exceeded maximum iterations (${maxIterations}). Possible infinite loop.`
|
2226
|
+
);
|
2227
|
+
}
|
2066
2228
|
context.set("_index", index);
|
2067
2229
|
const value = this.parseAttribute(
|
2068
2230
|
{ ...attr, repeat: void 0, "repeat-until": void 0 },
|
@@ -2137,16 +2299,34 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2137
2299
|
if (size < 0) {
|
2138
2300
|
throw new ParseError(`size must be non-negative, got ${size}`);
|
2139
2301
|
}
|
2302
|
+
if (stream.pos + size > stream.size) {
|
2303
|
+
throw new ParseError(
|
2304
|
+
`Not enough data: need ${size} bytes at position ${stream.pos}, but only ${stream.size - stream.pos} bytes available`
|
2305
|
+
);
|
2306
|
+
}
|
2140
2307
|
if (type === "str" || !type) {
|
2141
2308
|
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2309
|
+
let data;
|
2142
2310
|
if (type === "str") {
|
2143
|
-
|
2311
|
+
data = stream.readBytes(size);
|
2312
|
+
if (attr.process) {
|
2313
|
+
data = this.applyProcessing(data, attr.process);
|
2314
|
+
}
|
2315
|
+
return new TextDecoder(encoding).decode(data);
|
2144
2316
|
} else {
|
2145
|
-
|
2317
|
+
data = stream.readBytes(size);
|
2318
|
+
if (attr.process) {
|
2319
|
+
data = this.applyProcessing(data, attr.process);
|
2320
|
+
}
|
2321
|
+
return data;
|
2146
2322
|
}
|
2147
2323
|
} else {
|
2148
|
-
|
2149
|
-
|
2324
|
+
let data = stream.readBytes(size);
|
2325
|
+
if (attr.process) {
|
2326
|
+
data = this.applyProcessing(data, attr.process);
|
2327
|
+
}
|
2328
|
+
const substream = new KaitaiStream(data);
|
2329
|
+
return this.parseType(type, substream, context, attr["type-args"]);
|
2150
2330
|
}
|
2151
2331
|
}
|
2152
2332
|
if (attr["size-eos"]) {
|
@@ -2161,7 +2341,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2161
2341
|
if (!type) {
|
2162
2342
|
throw new ParseError("Attribute must have either type, size, or contents");
|
2163
2343
|
}
|
2164
|
-
return this.parseType(type, stream, context);
|
2344
|
+
return this.parseType(type, stream, context, attr["type-args"]);
|
2165
2345
|
}
|
2166
2346
|
/**
|
2167
2347
|
* Parse a value of a specific type.
|
@@ -2169,12 +2349,17 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2169
2349
|
* @param type - Type name or switch specification
|
2170
2350
|
* @param stream - Stream to read from
|
2171
2351
|
* @param context - Execution context
|
2352
|
+
* @param typeArgs - Arguments for parametric types
|
2172
2353
|
* @returns Parsed value
|
2173
2354
|
* @private
|
2174
2355
|
*/
|
2175
|
-
parseType(type, stream, context) {
|
2356
|
+
parseType(type, stream, context, typeArgs) {
|
2176
2357
|
if (typeof type === "object") {
|
2177
|
-
|
2358
|
+
return this.parseSwitchType(
|
2359
|
+
type,
|
2360
|
+
stream,
|
2361
|
+
context
|
2362
|
+
);
|
2178
2363
|
}
|
2179
2364
|
if (isBuiltinType(type)) {
|
2180
2365
|
return this.parseBuiltinType(type, stream, context);
|
@@ -2185,11 +2370,46 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2185
2370
|
if (this.schema.enums && !typeSchema.enums) {
|
2186
2371
|
typeSchema.enums = this.schema.enums;
|
2187
2372
|
}
|
2373
|
+
if (this.schema.types && !typeSchema.types) {
|
2374
|
+
typeSchema.types = this.schema.types;
|
2375
|
+
}
|
2188
2376
|
const interpreter = new _TypeInterpreter(typeSchema, meta);
|
2189
|
-
return interpreter.parse(stream, context.current);
|
2377
|
+
return interpreter.parse(stream, context.current, typeArgs);
|
2190
2378
|
}
|
2191
2379
|
throw new ParseError(`Unknown type: ${type}`);
|
2192
2380
|
}
|
2381
|
+
/**
|
2382
|
+
* Parse a switch type (type selection based on expression).
|
2383
|
+
*
|
2384
|
+
* @param switchType - Switch type specification
|
2385
|
+
* @param stream - Stream to read from
|
2386
|
+
* @param context - Execution context
|
2387
|
+
* @returns Parsed value
|
2388
|
+
* @private
|
2389
|
+
*/
|
2390
|
+
parseSwitchType(switchType, stream, context) {
|
2391
|
+
const switchOn = switchType["switch-on"];
|
2392
|
+
const cases = switchType["cases"];
|
2393
|
+
const defaultType = switchType["default"];
|
2394
|
+
if (!switchOn || typeof switchOn !== "string") {
|
2395
|
+
throw new ParseError("switch-on expression is required for switch types");
|
2396
|
+
}
|
2397
|
+
if (!cases) {
|
2398
|
+
throw new ParseError("cases are required for switch types");
|
2399
|
+
}
|
2400
|
+
const switchValue = this.evaluateValue(switchOn, context);
|
2401
|
+
const switchKey = String(switchValue);
|
2402
|
+
let selectedType = cases[switchKey];
|
2403
|
+
if (selectedType === void 0 && defaultType) {
|
2404
|
+
selectedType = defaultType;
|
2405
|
+
}
|
2406
|
+
if (selectedType === void 0) {
|
2407
|
+
throw new ParseError(
|
2408
|
+
`No matching case for switch value "${switchKey}" and no default type specified`
|
2409
|
+
);
|
2410
|
+
}
|
2411
|
+
return this.parseType(selectedType, stream, context);
|
2412
|
+
}
|
2193
2413
|
/**
|
2194
2414
|
* Parse a built-in type.
|
2195
2415
|
*
|
@@ -2205,6 +2425,13 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2205
2425
|
const meta = this.schema.meta || this.parentMeta;
|
2206
2426
|
const metaEndian = meta?.endian;
|
2207
2427
|
const endian = typeEndian || (typeof metaEndian === "string" ? metaEndian : "le");
|
2428
|
+
if (type.startsWith("b") && type.length > 1) {
|
2429
|
+
const bits = parseInt(type.substring(1), 10);
|
2430
|
+
if (!isNaN(bits) && bits >= 1 && bits <= 64) {
|
2431
|
+
const value = endian === "be" ? stream.readBitsIntBe(bits) : stream.readBitsIntLe(bits);
|
2432
|
+
return bits <= 32 ? Number(value) : value;
|
2433
|
+
}
|
2434
|
+
}
|
2208
2435
|
if (isIntegerType(type)) {
|
2209
2436
|
return this.readInteger(base, endian, stream);
|
2210
2437
|
}
|
@@ -2267,11 +2494,27 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2267
2494
|
}
|
2268
2495
|
}
|
2269
2496
|
/**
|
2270
|
-
*
|
2271
|
-
*
|
2272
|
-
*
|
2497
|
+
* Apply processing transformation to data.
|
2498
|
+
* Supports basic transformations like zlib decompression.
|
2499
|
+
*
|
2500
|
+
* @param data - Data to process
|
2501
|
+
* @param process - Processing specification
|
2502
|
+
* @returns Processed data
|
2503
|
+
* @private
|
2504
|
+
*/
|
2505
|
+
applyProcessing(data, process) {
|
2506
|
+
const processType = typeof process === "string" ? process : process.algorithm;
|
2507
|
+
if (processType) {
|
2508
|
+
throw new NotImplementedError(
|
2509
|
+
`Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
|
2510
|
+
);
|
2511
|
+
}
|
2512
|
+
return data;
|
2513
|
+
}
|
2514
|
+
/**
|
2515
|
+
* Evaluate a value that can be an expression or literal.
|
2273
2516
|
*
|
2274
|
-
* @param value -
|
2517
|
+
* @param value - Value to evaluate (expression string, number, or boolean)
|
2275
2518
|
* @param context - Execution context
|
2276
2519
|
* @returns Evaluated result
|
2277
2520
|
* @private
|
@@ -2284,8 +2527,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2284
2527
|
return value;
|
2285
2528
|
}
|
2286
2529
|
if (typeof value === "string") {
|
2530
|
+
if (!value.includes("_") && !value.includes(".")) {
|
2531
|
+
const cached = this.expressionCache.get(value);
|
2532
|
+
if (cached !== void 0) {
|
2533
|
+
return cached;
|
2534
|
+
}
|
2535
|
+
}
|
2287
2536
|
try {
|
2288
|
-
|
2537
|
+
const result = evaluateExpression(value, context);
|
2538
|
+
if (!value.includes("_") && !value.includes(".")) {
|
2539
|
+
if (this.expressionCache.size >= this.MAX_CACHE_SIZE) {
|
2540
|
+
this.expressionCache.clear();
|
2541
|
+
}
|
2542
|
+
this.expressionCache.set(value, result);
|
2543
|
+
}
|
2544
|
+
return result;
|
2289
2545
|
} catch (error) {
|
2290
2546
|
throw new ParseError(
|
2291
2547
|
`Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
|
@@ -2426,7 +2682,7 @@ function parse(ksyYaml, buffer, options = {}) {
|
|
2426
2682
|
* @module kaitai-struct-ts
|
2427
2683
|
* @author Fabiano Pinto
|
2428
2684
|
* @license MIT
|
2429
|
-
* @version 0.
|
2685
|
+
* @version 0.6.0
|
2430
2686
|
*
|
2431
2687
|
* @description
|
2432
2688
|
* A runtime interpreter for Kaitai Struct binary format definitions in TypeScript.
|