@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.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
  *
@@ -1815,9 +1877,7 @@ var Evaluator = class {
1815
1877
  evaluateEnumAccess(enumName, valueName, context) {
1816
1878
  const value = context.getEnumValue(enumName, valueName);
1817
1879
  if (value === void 0) {
1818
- throw new ParseError(
1819
- `Enum value "${enumName}::${valueName}" not found`
1820
- );
1880
+ throw new ParseError(`Enum value "${enumName}::${valueName}" not found`);
1821
1881
  }
1822
1882
  return value;
1823
1883
  }
@@ -1914,6 +1974,10 @@ var TypeInterpreter = class _TypeInterpreter {
1914
1974
  constructor(schema, parentMeta) {
1915
1975
  this.schema = schema;
1916
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;
1917
1981
  if (!schema.meta && !parentMeta) {
1918
1982
  throw new ParseError("Schema must have meta section");
1919
1983
  }
@@ -1926,12 +1990,21 @@ var TypeInterpreter = class _TypeInterpreter {
1926
1990
  *
1927
1991
  * @param stream - Binary stream to parse
1928
1992
  * @param parent - Parent object (for nested types)
1993
+ * @param typeArgs - Arguments for parametric types
1929
1994
  * @returns Parsed object
1930
1995
  */
1931
- parse(stream, parent) {
1996
+ parse(stream, parent, typeArgs) {
1932
1997
  const result = {};
1933
1998
  const context = new Context(stream, result, parent, this.schema.enums);
1934
1999
  context.current = result;
2000
+ if (typeArgs && this.schema.params) {
2001
+ for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
2002
+ const param = this.schema.params[i];
2003
+ const argValue = typeArgs[i];
2004
+ const evaluatedArg = typeof argValue === "string" ? this.evaluateValue(argValue, context) : argValue;
2005
+ context.set(param.id, evaluatedArg);
2006
+ }
2007
+ }
1935
2008
  if (this.schema.seq) {
1936
2009
  for (const attr of this.schema.seq) {
1937
2010
  const value = this.parseAttribute(attr, context);
@@ -1940,8 +2013,86 @@ var TypeInterpreter = class _TypeInterpreter {
1940
2013
  }
1941
2014
  }
1942
2015
  }
2016
+ if (this.schema.instances) {
2017
+ this.setupInstances(result, stream, context);
2018
+ }
1943
2019
  return result;
1944
2020
  }
2021
+ /**
2022
+ * Set up lazy-evaluated instance getters.
2023
+ * Instances are computed on first access and cached.
2024
+ *
2025
+ * @param result - Result object to add getters to
2026
+ * @param stream - Stream for parsing
2027
+ * @param context - Execution context
2028
+ * @private
2029
+ */
2030
+ setupInstances(result, stream, context) {
2031
+ if (!this.schema.instances) return;
2032
+ for (const [name, instance] of Object.entries(this.schema.instances)) {
2033
+ let cached = void 0;
2034
+ let evaluated = false;
2035
+ Object.defineProperty(result, name, {
2036
+ get: () => {
2037
+ if (!evaluated) {
2038
+ cached = this.parseInstance(
2039
+ instance,
2040
+ stream,
2041
+ context
2042
+ );
2043
+ evaluated = true;
2044
+ }
2045
+ return cached;
2046
+ },
2047
+ enumerable: true,
2048
+ configurable: true
2049
+ });
2050
+ }
2051
+ }
2052
+ /**
2053
+ * Parse an instance (lazy-evaluated field).
2054
+ *
2055
+ * @param instance - Instance specification
2056
+ * @param stream - Stream to read from
2057
+ * @param context - Execution context
2058
+ * @returns Parsed or calculated value
2059
+ * @private
2060
+ */
2061
+ parseInstance(instance, stream, context) {
2062
+ if ("value" in instance) {
2063
+ return this.evaluateValue(
2064
+ instance.value,
2065
+ context
2066
+ );
2067
+ }
2068
+ const savedPos = stream.pos;
2069
+ try {
2070
+ if (instance.pos !== void 0) {
2071
+ const pos = this.evaluateValue(
2072
+ instance.pos,
2073
+ context
2074
+ );
2075
+ if (typeof pos === "number") {
2076
+ stream.seek(pos);
2077
+ } else if (typeof pos === "bigint") {
2078
+ stream.seek(Number(pos));
2079
+ } else {
2080
+ throw new ParseError(
2081
+ `pos must evaluate to a number, got ${typeof pos}`
2082
+ );
2083
+ }
2084
+ }
2085
+ const value = this.parseAttribute(
2086
+ instance,
2087
+ context
2088
+ );
2089
+ return value;
2090
+ } finally {
2091
+ if (instance.pos !== void 0) {
2092
+ stream.seek(savedPos);
2093
+ }
2094
+ }
2095
+ }
1945
2096
  /**
1946
2097
  * Parse a single attribute according to its specification.
1947
2098
  *
@@ -1971,6 +2122,11 @@ var TypeInterpreter = class _TypeInterpreter {
1971
2122
  if (attr.io) {
1972
2123
  throw new NotImplementedError("Custom I/O streams");
1973
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
+ }
1974
2130
  if (attr.repeat) {
1975
2131
  return this.parseRepeated(attr, context);
1976
2132
  }
@@ -2020,7 +2176,13 @@ var TypeInterpreter = class _TypeInterpreter {
2020
2176
  throw new ParseError("repeat-until expression is required");
2021
2177
  }
2022
2178
  let index = 0;
2179
+ const maxIterations = 1e6;
2023
2180
  while (true) {
2181
+ if (index >= maxIterations) {
2182
+ throw new ParseError(
2183
+ `repeat-until exceeded maximum iterations (${maxIterations}). Possible infinite loop.`
2184
+ );
2185
+ }
2024
2186
  context.set("_index", index);
2025
2187
  const value = this.parseAttribute(
2026
2188
  { ...attr, repeat: void 0, "repeat-until": void 0 },
@@ -2095,16 +2257,34 @@ var TypeInterpreter = class _TypeInterpreter {
2095
2257
  if (size < 0) {
2096
2258
  throw new ParseError(`size must be non-negative, got ${size}`);
2097
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
+ }
2098
2265
  if (type === "str" || !type) {
2099
2266
  const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2267
+ let data;
2100
2268
  if (type === "str") {
2101
- return stream.readStr(size, encoding);
2269
+ data = stream.readBytes(size);
2270
+ if (attr.process) {
2271
+ data = this.applyProcessing(data, attr.process);
2272
+ }
2273
+ return new TextDecoder(encoding).decode(data);
2102
2274
  } else {
2103
- return stream.readBytes(size);
2275
+ data = stream.readBytes(size);
2276
+ if (attr.process) {
2277
+ data = this.applyProcessing(data, attr.process);
2278
+ }
2279
+ return data;
2104
2280
  }
2105
2281
  } else {
2106
- const substream = stream.substream(size);
2107
- return this.parseType(type, substream, context);
2282
+ let data = stream.readBytes(size);
2283
+ if (attr.process) {
2284
+ data = this.applyProcessing(data, attr.process);
2285
+ }
2286
+ const substream = new KaitaiStream(data);
2287
+ return this.parseType(type, substream, context, attr["type-args"]);
2108
2288
  }
2109
2289
  }
2110
2290
  if (attr["size-eos"]) {
@@ -2119,7 +2299,7 @@ var TypeInterpreter = class _TypeInterpreter {
2119
2299
  if (!type) {
2120
2300
  throw new ParseError("Attribute must have either type, size, or contents");
2121
2301
  }
2122
- return this.parseType(type, stream, context);
2302
+ return this.parseType(type, stream, context, attr["type-args"]);
2123
2303
  }
2124
2304
  /**
2125
2305
  * Parse a value of a specific type.
@@ -2127,12 +2307,17 @@ var TypeInterpreter = class _TypeInterpreter {
2127
2307
  * @param type - Type name or switch specification
2128
2308
  * @param stream - Stream to read from
2129
2309
  * @param context - Execution context
2310
+ * @param typeArgs - Arguments for parametric types
2130
2311
  * @returns Parsed value
2131
2312
  * @private
2132
2313
  */
2133
- parseType(type, stream, context) {
2314
+ parseType(type, stream, context, typeArgs) {
2134
2315
  if (typeof type === "object") {
2135
- throw new NotImplementedError("Switch types");
2316
+ return this.parseSwitchType(
2317
+ type,
2318
+ stream,
2319
+ context
2320
+ );
2136
2321
  }
2137
2322
  if (isBuiltinType(type)) {
2138
2323
  return this.parseBuiltinType(type, stream, context);
@@ -2143,11 +2328,46 @@ var TypeInterpreter = class _TypeInterpreter {
2143
2328
  if (this.schema.enums && !typeSchema.enums) {
2144
2329
  typeSchema.enums = this.schema.enums;
2145
2330
  }
2331
+ if (this.schema.types && !typeSchema.types) {
2332
+ typeSchema.types = this.schema.types;
2333
+ }
2146
2334
  const interpreter = new _TypeInterpreter(typeSchema, meta);
2147
- return interpreter.parse(stream, context.current);
2335
+ return interpreter.parse(stream, context.current, typeArgs);
2148
2336
  }
2149
2337
  throw new ParseError(`Unknown type: ${type}`);
2150
2338
  }
2339
+ /**
2340
+ * Parse a switch type (type selection based on expression).
2341
+ *
2342
+ * @param switchType - Switch type specification
2343
+ * @param stream - Stream to read from
2344
+ * @param context - Execution context
2345
+ * @returns Parsed value
2346
+ * @private
2347
+ */
2348
+ parseSwitchType(switchType, stream, context) {
2349
+ const switchOn = switchType["switch-on"];
2350
+ const cases = switchType["cases"];
2351
+ const defaultType = switchType["default"];
2352
+ if (!switchOn || typeof switchOn !== "string") {
2353
+ throw new ParseError("switch-on expression is required for switch types");
2354
+ }
2355
+ if (!cases) {
2356
+ throw new ParseError("cases are required for switch types");
2357
+ }
2358
+ const switchValue = this.evaluateValue(switchOn, context);
2359
+ const switchKey = String(switchValue);
2360
+ let selectedType = cases[switchKey];
2361
+ if (selectedType === void 0 && defaultType) {
2362
+ selectedType = defaultType;
2363
+ }
2364
+ if (selectedType === void 0) {
2365
+ throw new ParseError(
2366
+ `No matching case for switch value "${switchKey}" and no default type specified`
2367
+ );
2368
+ }
2369
+ return this.parseType(selectedType, stream, context);
2370
+ }
2151
2371
  /**
2152
2372
  * Parse a built-in type.
2153
2373
  *
@@ -2163,6 +2383,13 @@ var TypeInterpreter = class _TypeInterpreter {
2163
2383
  const meta = this.schema.meta || this.parentMeta;
2164
2384
  const metaEndian = meta?.endian;
2165
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
+ }
2166
2393
  if (isIntegerType(type)) {
2167
2394
  return this.readInteger(base, endian, stream);
2168
2395
  }
@@ -2225,11 +2452,27 @@ var TypeInterpreter = class _TypeInterpreter {
2225
2452
  }
2226
2453
  }
2227
2454
  /**
2228
- * Evaluate an expression or return a literal value.
2229
- * If the value is a string, it's treated as an expression.
2230
- * If it's a number or boolean, it's returned as-is.
2455
+ * Apply processing transformation to data.
2456
+ * Supports basic transformations like zlib decompression.
2457
+ *
2458
+ * @param data - Data to process
2459
+ * @param process - Processing specification
2460
+ * @returns Processed data
2461
+ * @private
2462
+ */
2463
+ applyProcessing(data, process) {
2464
+ const processType = typeof process === "string" ? process : process.algorithm;
2465
+ if (processType) {
2466
+ throw new NotImplementedError(
2467
+ `Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
2468
+ );
2469
+ }
2470
+ return data;
2471
+ }
2472
+ /**
2473
+ * Evaluate a value that can be an expression or literal.
2231
2474
  *
2232
- * @param value - Expression string or literal value
2475
+ * @param value - Value to evaluate (expression string, number, or boolean)
2233
2476
  * @param context - Execution context
2234
2477
  * @returns Evaluated result
2235
2478
  * @private
@@ -2242,8 +2485,21 @@ var TypeInterpreter = class _TypeInterpreter {
2242
2485
  return value;
2243
2486
  }
2244
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
+ }
2245
2494
  try {
2246
- 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;
2247
2503
  } catch (error) {
2248
2504
  throw new ParseError(
2249
2505
  `Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
@@ -2383,7 +2639,7 @@ export {
2383
2639
  * @module kaitai-struct-ts
2384
2640
  * @author Fabiano Pinto
2385
2641
  * @license MIT
2386
- * @version 0.2.0
2642
+ * @version 0.6.0
2387
2643
  *
2388
2644
  * @description
2389
2645
  * A runtime interpreter for Kaitai Struct binary format definitions in TypeScript.