@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/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.buffer.length;
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.buffer.length;
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
- return stream.readStr(size, encoding);
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
- return stream.readBytes(size);
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
- const substream = stream.substream(size);
2149
- return this.parseType(type, substream, context);
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
- throw new NotImplementedError("Switch types");
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
- * Evaluate an expression or return a literal value.
2271
- * If the value is a string, it's treated as an expression.
2272
- * If it's a number or boolean, it's returned as-is.
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 - Expression string or literal 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
- return evaluateExpression(value, context);
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.2.0
2685
+ * @version 0.6.0
2430
2686
  *
2431
2687
  * @description
2432
2688
  * A runtime interpreter for Kaitai Struct binary format definitions in TypeScript.