@k67/kaitai-struct-ts 0.5.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.mjs CHANGED
@@ -151,6 +151,7 @@ var KaitaiStream = class _KaitaiStream {
151
151
  if (buffer instanceof ArrayBuffer) {
152
152
  this.buffer = new Uint8Array(buffer);
153
153
  this.view = new DataView(buffer);
154
+ this._size = buffer.byteLength;
154
155
  } else {
155
156
  this.buffer = buffer;
156
157
  this.view = new DataView(
@@ -158,6 +159,7 @@ var KaitaiStream = class _KaitaiStream {
158
159
  buffer.byteOffset,
159
160
  buffer.byteLength
160
161
  );
162
+ this._size = buffer.byteLength;
161
163
  }
162
164
  }
163
165
  /**
@@ -174,13 +176,13 @@ var KaitaiStream = class _KaitaiStream {
174
176
  * Total size of the stream in bytes
175
177
  */
176
178
  get size() {
177
- return this.buffer.length;
179
+ return this._size;
178
180
  }
179
181
  /**
180
182
  * Check if we've reached the end of the stream
181
183
  */
182
184
  isEof() {
183
- return this._pos >= this.buffer.length;
185
+ return this._pos >= this._size;
184
186
  }
185
187
  /**
186
188
  * Seek to a specific position in the stream
@@ -600,6 +602,7 @@ var KsyParser = class {
600
602
  throw new ParseError("KSY file must contain an object");
601
603
  }
602
604
  const schema = parsed;
605
+ this.processParametricTypes(schema);
603
606
  if (validate) {
604
607
  const result = this.validate(schema, { strict });
605
608
  if (!result.valid) {
@@ -617,6 +620,65 @@ var KsyParser = class {
617
620
  }
618
621
  return schema;
619
622
  }
623
+ /**
624
+ * Process parametric type syntax in schema.
625
+ * Converts "type_name(arg1, arg2)" to structured format.
626
+ *
627
+ * @param schema - Schema to process
628
+ * @private
629
+ */
630
+ processParametricTypes(schema) {
631
+ if (schema.seq) {
632
+ for (const attr of schema.seq) {
633
+ if (typeof attr.type === "string") {
634
+ this.parseParametricType(attr);
635
+ }
636
+ }
637
+ }
638
+ if (schema.instances) {
639
+ for (const instance of Object.values(schema.instances)) {
640
+ if (typeof instance.type === "string") {
641
+ this.parseParametricType(instance);
642
+ }
643
+ }
644
+ }
645
+ if (schema.types) {
646
+ for (const type of Object.values(schema.types)) {
647
+ this.processParametricTypes(type);
648
+ }
649
+ }
650
+ }
651
+ /**
652
+ * Parse parametric type syntax from a type string.
653
+ * Converts "type_name(arg1, arg2)" to type + type-args.
654
+ *
655
+ * @param attr - Attribute to process
656
+ * @private
657
+ */
658
+ parseParametricType(attr) {
659
+ if (typeof attr.type !== "string") return;
660
+ const match = attr.type.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
661
+ if (!match) return;
662
+ const [, typeName, argsStr] = match;
663
+ const args = [];
664
+ if (argsStr.trim()) {
665
+ const argParts = argsStr.split(",").map((s) => s.trim());
666
+ for (const arg of argParts) {
667
+ const num = Number(arg);
668
+ if (!isNaN(num)) {
669
+ args.push(num);
670
+ } else if (arg === "true") {
671
+ args.push(true);
672
+ } else if (arg === "false") {
673
+ args.push(false);
674
+ } else {
675
+ args.push(arg);
676
+ }
677
+ }
678
+ }
679
+ attr.type = typeName;
680
+ attr["type-args"] = args;
681
+ }
620
682
  /**
621
683
  * Validate a schema object.
622
684
  *
@@ -1912,6 +1974,10 @@ var TypeInterpreter = class _TypeInterpreter {
1912
1974
  constructor(schema, parentMeta) {
1913
1975
  this.schema = schema;
1914
1976
  this.parentMeta = parentMeta;
1977
+ // Performance optimization: cache for constant expressions
1978
+ // Limited size to prevent memory leaks
1979
+ this.expressionCache = /* @__PURE__ */ new Map();
1980
+ this.MAX_CACHE_SIZE = 1e3;
1915
1981
  if (!schema.meta && !parentMeta) {
1916
1982
  throw new ParseError("Schema must have meta section");
1917
1983
  }
@@ -2016,7 +2082,10 @@ var TypeInterpreter = class _TypeInterpreter {
2016
2082
  );
2017
2083
  }
2018
2084
  }
2019
- const value = this.parseAttribute(instance, context);
2085
+ const value = this.parseAttribute(
2086
+ instance,
2087
+ context
2088
+ );
2020
2089
  return value;
2021
2090
  } finally {
2022
2091
  if (instance.pos !== void 0) {
@@ -2053,6 +2122,11 @@ var TypeInterpreter = class _TypeInterpreter {
2053
2122
  if (attr.io) {
2054
2123
  throw new NotImplementedError("Custom I/O streams");
2055
2124
  }
2125
+ if (!attr.type && !attr.size && !attr["size-eos"] && !attr.contents) {
2126
+ throw new ParseError(
2127
+ `Attribute "${attr.id || "unknown"}" must have type, size, size-eos, or contents`
2128
+ );
2129
+ }
2056
2130
  if (attr.repeat) {
2057
2131
  return this.parseRepeated(attr, context);
2058
2132
  }
@@ -2102,7 +2176,13 @@ var TypeInterpreter = class _TypeInterpreter {
2102
2176
  throw new ParseError("repeat-until expression is required");
2103
2177
  }
2104
2178
  let index = 0;
2179
+ const maxIterations = 1e6;
2105
2180
  while (true) {
2181
+ if (index >= maxIterations) {
2182
+ throw new ParseError(
2183
+ `repeat-until exceeded maximum iterations (${maxIterations}). Possible infinite loop.`
2184
+ );
2185
+ }
2106
2186
  context.set("_index", index);
2107
2187
  const value = this.parseAttribute(
2108
2188
  { ...attr, repeat: void 0, "repeat-until": void 0 },
@@ -2177,6 +2257,11 @@ var TypeInterpreter = class _TypeInterpreter {
2177
2257
  if (size < 0) {
2178
2258
  throw new ParseError(`size must be non-negative, got ${size}`);
2179
2259
  }
2260
+ if (stream.pos + size > stream.size) {
2261
+ throw new ParseError(
2262
+ `Not enough data: need ${size} bytes at position ${stream.pos}, but only ${stream.size - stream.pos} bytes available`
2263
+ );
2264
+ }
2180
2265
  if (type === "str" || !type) {
2181
2266
  const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2182
2267
  let data;
@@ -2298,6 +2383,13 @@ var TypeInterpreter = class _TypeInterpreter {
2298
2383
  const meta = this.schema.meta || this.parentMeta;
2299
2384
  const metaEndian = meta?.endian;
2300
2385
  const endian = typeEndian || (typeof metaEndian === "string" ? metaEndian : "le");
2386
+ if (type.startsWith("b") && type.length > 1) {
2387
+ const bits = parseInt(type.substring(1), 10);
2388
+ if (!isNaN(bits) && bits >= 1 && bits <= 64) {
2389
+ const value = endian === "be" ? stream.readBitsIntBe(bits) : stream.readBitsIntLe(bits);
2390
+ return bits <= 32 ? Number(value) : value;
2391
+ }
2392
+ }
2301
2393
  if (isIntegerType(type)) {
2302
2394
  return this.readInteger(base, endian, stream);
2303
2395
  }
@@ -2393,8 +2485,21 @@ var TypeInterpreter = class _TypeInterpreter {
2393
2485
  return value;
2394
2486
  }
2395
2487
  if (typeof value === "string") {
2488
+ if (!value.includes("_") && !value.includes(".")) {
2489
+ const cached = this.expressionCache.get(value);
2490
+ if (cached !== void 0) {
2491
+ return cached;
2492
+ }
2493
+ }
2396
2494
  try {
2397
- return evaluateExpression(value, context);
2495
+ const result = evaluateExpression(value, context);
2496
+ if (!value.includes("_") && !value.includes(".")) {
2497
+ if (this.expressionCache.size >= this.MAX_CACHE_SIZE) {
2498
+ this.expressionCache.clear();
2499
+ }
2500
+ this.expressionCache.set(value, result);
2501
+ }
2502
+ return result;
2398
2503
  } catch (error) {
2399
2504
  throw new ParseError(
2400
2505
  `Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
@@ -2534,7 +2639,7 @@ export {
2534
2639
  * @module kaitai-struct-ts
2535
2640
  * @author Fabiano Pinto
2536
2641
  * @license MIT
2537
- * @version 0.2.0
2642
+ * @version 0.6.0
2538
2643
  *
2539
2644
  * @description
2540
2645
  * A runtime interpreter for Kaitai Struct binary format definitions in TypeScript.