@k67/kaitai-struct-ts 0.8.0 → 0.10.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
@@ -1,29 +1,67 @@
1
1
  // src/utils/errors.ts
2
2
  var KaitaiError = class _KaitaiError extends Error {
3
- constructor(message, position) {
4
- super(message);
3
+ constructor(message, position, context) {
4
+ super(_KaitaiError.formatMessage(message, position, context));
5
5
  this.position = position;
6
+ this.context = context;
6
7
  this.name = "KaitaiError";
7
8
  Object.setPrototypeOf(this, _KaitaiError.prototype);
8
9
  }
10
+ /**
11
+ * Format error message with position and context.
12
+ * @private
13
+ */
14
+ static formatMessage(message, position, context) {
15
+ let formatted = message;
16
+ if (position !== void 0) {
17
+ formatted += ` (at byte offset 0x${position.toString(16).toUpperCase()})`;
18
+ }
19
+ if (context && context.length > 0) {
20
+ const hexContext = _KaitaiError.formatHexContext(context, position);
21
+ formatted += `
22
+ ${hexContext}`;
23
+ }
24
+ return formatted;
25
+ }
26
+ /**
27
+ * Format hex dump context around error position.
28
+ * @private
29
+ */
30
+ static formatHexContext(data, position) {
31
+ const contextSize = 16;
32
+ const start = Math.max(0, (position ?? 0) - contextSize);
33
+ const end = Math.min(data.length, (position ?? 0) + contextSize);
34
+ const chunk = data.slice(start, end);
35
+ const lines = ["Context:"];
36
+ let offset = start;
37
+ for (let i = 0; i < chunk.length; i += 16) {
38
+ const lineBytes = chunk.slice(i, i + 16);
39
+ const hex = Array.from(lineBytes).map((b) => b.toString(16).padStart(2, "0")).join(" ");
40
+ const ascii = Array.from(lineBytes).map((b) => b >= 32 && b <= 126 ? String.fromCharCode(b) : ".").join("");
41
+ const offsetStr = ` ${(offset + i).toString(16).padStart(8, "0")}`;
42
+ const marker = position !== void 0 && position >= offset + i && position < offset + i + lineBytes.length ? " <--" : "";
43
+ lines.push(`${offsetStr}: ${hex.padEnd(48, " ")} | ${ascii}${marker}`);
44
+ }
45
+ return lines.join("\n");
46
+ }
9
47
  };
10
48
  var ValidationError = class _ValidationError extends KaitaiError {
11
- constructor(message, position) {
12
- super(message, position);
49
+ constructor(message, position, context) {
50
+ super(message, position, context);
13
51
  this.name = "ValidationError";
14
52
  Object.setPrototypeOf(this, _ValidationError.prototype);
15
53
  }
16
54
  };
17
55
  var ParseError = class _ParseError extends KaitaiError {
18
- constructor(message, position) {
19
- super(message, position);
56
+ constructor(message, position, context) {
57
+ super(message, position, context);
20
58
  this.name = "ParseError";
21
59
  Object.setPrototypeOf(this, _ParseError.prototype);
22
60
  }
23
61
  };
24
62
  var EOFError = class _EOFError extends KaitaiError {
25
- constructor(message = "Unexpected end of stream", position) {
26
- super(message, position);
63
+ constructor(message = "Unexpected end of stream", position, context) {
64
+ super(message, position, context);
27
65
  this.name = "EOFError";
28
66
  Object.setPrototypeOf(this, _EOFError.prototype);
29
67
  }
@@ -549,7 +587,9 @@ var BUILTIN_TYPES = [
549
587
  "strz"
550
588
  ];
551
589
  function isBuiltinType(type) {
552
- return BUILTIN_TYPES.includes(type);
590
+ if (BUILTIN_TYPES.includes(type)) return true;
591
+ if (/^b\d+$/.test(type)) return true;
592
+ return false;
553
593
  }
554
594
  function getTypeEndianness(type) {
555
595
  if (type.endsWith("le")) return "le";
@@ -809,11 +849,83 @@ var KsyParser = class {
809
849
  * @param imports - Map of import names to their YAML content
810
850
  * @param options - Parsing options
811
851
  * @returns Parsed schema with resolved imports
812
- */
813
- parseWithImports(mainYaml, _imports, options = {}) {
852
+ * @throws {ParseError} If import resolution fails
853
+ * @example
854
+ * ```typescript
855
+ * const parser = new KsyParser()
856
+ * const imports = new Map([
857
+ * ['/common/riff', riffYamlContent]
858
+ * ])
859
+ * const schema = parser.parseWithImports(wavYaml, imports)
860
+ * ```
861
+ */
862
+ parseWithImports(mainYaml, imports, options = {}) {
814
863
  const mainSchema = this.parse(mainYaml, options);
864
+ if (!mainSchema.meta.imports || mainSchema.meta.imports.length === 0) {
865
+ return mainSchema;
866
+ }
867
+ const resolvedTypes = {};
868
+ for (const importPath of mainSchema.meta.imports) {
869
+ if (!imports.has(importPath)) {
870
+ throw new ParseError(
871
+ `Import not found: ${importPath}. Available imports: ${Array.from(imports.keys()).join(", ")}`
872
+ );
873
+ }
874
+ const importYaml = imports.get(importPath);
875
+ const importedSchema = this.parse(importYaml, {
876
+ ...options,
877
+ validate: false
878
+ // Skip validation for imported schemas
879
+ });
880
+ const namespace = this.extractNamespace(importPath);
881
+ if (importedSchema.types) {
882
+ for (const [typeName, typeSchema] of Object.entries(
883
+ importedSchema.types
884
+ )) {
885
+ const qualifiedName = `${namespace}::${typeName}`;
886
+ resolvedTypes[qualifiedName] = typeSchema;
887
+ }
888
+ }
889
+ resolvedTypes[namespace] = {
890
+ meta: importedSchema.meta,
891
+ seq: importedSchema.seq,
892
+ instances: importedSchema.instances,
893
+ types: importedSchema.types,
894
+ enums: importedSchema.enums
895
+ };
896
+ if (importedSchema.enums) {
897
+ if (!mainSchema.enums) {
898
+ mainSchema.enums = {};
899
+ }
900
+ for (const [enumName, enumSpec] of Object.entries(
901
+ importedSchema.enums
902
+ )) {
903
+ const qualifiedEnumName = `${namespace}::${enumName}`;
904
+ mainSchema.enums[qualifiedEnumName] = enumSpec;
905
+ }
906
+ }
907
+ }
908
+ if (Object.keys(resolvedTypes).length > 0) {
909
+ mainSchema.types = {
910
+ ...resolvedTypes,
911
+ ...mainSchema.types
912
+ };
913
+ }
815
914
  return mainSchema;
816
915
  }
916
+ /**
917
+ * Extract namespace from import path.
918
+ * Converts paths like '/common/riff' or 'common/riff' to 'riff'.
919
+ *
920
+ * @param importPath - Import path from meta.imports
921
+ * @returns Namespace identifier
922
+ * @private
923
+ */
924
+ extractNamespace(importPath) {
925
+ const normalized = importPath.startsWith("/") ? importPath.slice(1) : importPath;
926
+ const segments = normalized.split("/");
927
+ return segments[segments.length - 1];
928
+ }
817
929
  };
818
930
 
819
931
  // src/interpreter/Context.ts
@@ -1124,6 +1236,17 @@ var Lexer = class {
1124
1236
  }
1125
1237
  return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
1126
1238
  }
1239
+ if (this.current === "0" && this.peek() === "b") {
1240
+ value += this.current;
1241
+ this.advance();
1242
+ value += this.current;
1243
+ this.advance();
1244
+ while (this.current !== null && /[01]/.test(this.current)) {
1245
+ value += this.current;
1246
+ this.advance();
1247
+ }
1248
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 2), start);
1249
+ }
1127
1250
  while (this.current !== null && this.isDigit(this.current)) {
1128
1251
  value += this.current;
1129
1252
  this.advance();
@@ -1335,6 +1458,9 @@ function createMethodCall(object, method, args) {
1335
1458
  function createEnumAccess(enumName, value) {
1336
1459
  return { kind: "EnumAccess", enumName, value };
1337
1460
  }
1461
+ function createArrayLiteral(elements) {
1462
+ return { kind: "ArrayLiteral", elements };
1463
+ }
1338
1464
 
1339
1465
  // src/expression/Parser.ts
1340
1466
  var ExpressionParser = class {
@@ -1631,6 +1757,17 @@ var ExpressionParser = class {
1631
1757
  this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
1632
1758
  return expr;
1633
1759
  }
1760
+ if (this.match("LBRACKET" /* LBRACKET */)) {
1761
+ const elements = [];
1762
+ if (this.current().type !== "RBRACKET" /* RBRACKET */) {
1763
+ elements.push(this.parseTernary());
1764
+ while (this.match("COMMA" /* COMMA */)) {
1765
+ elements.push(this.parseTernary());
1766
+ }
1767
+ }
1768
+ this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array literal");
1769
+ return createArrayLiteral(elements);
1770
+ }
1634
1771
  throw new ParseError(
1635
1772
  `Unexpected token: ${this.current().type}`,
1636
1773
  this.current().position
@@ -1669,6 +1806,8 @@ var Evaluator = class {
1669
1806
  return this.evaluateMethodCall(n.object, n.method, n.args, context);
1670
1807
  case "EnumAccess":
1671
1808
  return this.evaluateEnumAccess(n.enumName, n.value, context);
1809
+ case "ArrayLiteral":
1810
+ return this.evaluateArrayLiteral(n.elements, context);
1672
1811
  default:
1673
1812
  throw new ParseError(`Unknown AST node kind: ${node.kind}`);
1674
1813
  }
@@ -1714,15 +1853,15 @@ var Evaluator = class {
1714
1853
  return !this.equals(leftVal, rightVal);
1715
1854
  // Bitwise
1716
1855
  case "<<":
1717
- return this.toInt(leftVal) << this.toInt(rightVal);
1856
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a << b);
1718
1857
  case ">>":
1719
- return this.toInt(leftVal) >> this.toInt(rightVal);
1858
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a >> b);
1720
1859
  case "&":
1721
- return this.toInt(leftVal) & this.toInt(rightVal);
1860
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a & b);
1722
1861
  case "|":
1723
- return this.toInt(leftVal) | this.toInt(rightVal);
1862
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a | b);
1724
1863
  case "^":
1725
- return this.toInt(leftVal) ^ this.toInt(rightVal);
1864
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a ^ b);
1726
1865
  // Logical
1727
1866
  case "and":
1728
1867
  return this.toBoolean(leftVal) && this.toBoolean(rightVal);
@@ -1766,6 +1905,16 @@ var Evaluator = class {
1766
1905
  `Cannot access property ${property} of null/undefined`
1767
1906
  );
1768
1907
  }
1908
+ if (property === "to_i") {
1909
+ if (typeof obj === "number") return Math.floor(obj);
1910
+ if (typeof obj === "bigint") return Number(obj);
1911
+ if (typeof obj === "string") return parseInt(obj, 10);
1912
+ if (typeof obj === "boolean") return obj ? 1 : 0;
1913
+ return this.toInt(obj);
1914
+ }
1915
+ if (property === "to_s") {
1916
+ return String(obj);
1917
+ }
1769
1918
  if (typeof obj === "object") {
1770
1919
  return obj[property];
1771
1920
  }
@@ -1792,8 +1941,9 @@ var Evaluator = class {
1792
1941
  * Evaluate method call (object.method()).
1793
1942
  * @private
1794
1943
  */
1795
- evaluateMethodCall(object, method, _args, context) {
1944
+ evaluateMethodCall(object, method, args, context) {
1796
1945
  const obj = this.evaluate(object, context);
1946
+ const evalArgs = args.map((arg) => this.evaluate(arg, context));
1797
1947
  if (method === "length" || method === "size") {
1798
1948
  if (Array.isArray(obj)) return obj.length;
1799
1949
  if (obj instanceof Uint8Array) return obj.length;
@@ -1801,13 +1951,182 @@ var Evaluator = class {
1801
1951
  throw new ParseError(`Object does not have a ${method} property`);
1802
1952
  }
1803
1953
  if (method === "to_i") {
1954
+ const base = evalArgs.length > 0 ? this.toInt(evalArgs[0]) : 10;
1955
+ if (typeof obj === "string") {
1956
+ return parseInt(obj, base);
1957
+ }
1804
1958
  return this.toInt(obj);
1805
1959
  }
1806
1960
  if (method === "to_s") {
1807
1961
  return String(obj);
1808
1962
  }
1963
+ if (typeof obj === "string") {
1964
+ return this.evaluateStringMethod(obj, method, evalArgs);
1965
+ }
1966
+ if (Array.isArray(obj) || obj instanceof Uint8Array) {
1967
+ return this.evaluateArrayMethod(obj, method, evalArgs);
1968
+ }
1809
1969
  throw new ParseError(`Unknown method: ${method}`);
1810
1970
  }
1971
+ /**
1972
+ * Evaluate string methods.
1973
+ * @private
1974
+ */
1975
+ evaluateStringMethod(str, method, args) {
1976
+ switch (method) {
1977
+ case "substring": {
1978
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
1979
+ const end = args.length > 1 ? this.toInt(args[1]) : void 0;
1980
+ return str.substring(start, end);
1981
+ }
1982
+ case "substr": {
1983
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
1984
+ const length = args.length > 1 ? this.toInt(args[1]) : void 0;
1985
+ return str.substr(start, length);
1986
+ }
1987
+ case "reverse":
1988
+ return str.split("").reverse().join("");
1989
+ case "to_i": {
1990
+ const base = args.length > 0 ? this.toInt(args[0]) : 10;
1991
+ return parseInt(str, base);
1992
+ }
1993
+ case "length":
1994
+ case "size":
1995
+ return str.length;
1996
+ // Ruby-style string methods used in Kaitai
1997
+ case "upcase":
1998
+ case "to_upper":
1999
+ return str.toUpperCase();
2000
+ case "downcase":
2001
+ case "to_lower":
2002
+ return str.toLowerCase();
2003
+ case "capitalize":
2004
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
2005
+ case "strip":
2006
+ case "trim":
2007
+ return str.trim();
2008
+ case "lstrip":
2009
+ case "trim_start":
2010
+ return str.trimStart();
2011
+ case "rstrip":
2012
+ case "trim_end":
2013
+ return str.trimEnd();
2014
+ case "starts_with":
2015
+ case "startsWith": {
2016
+ if (args.length === 0) {
2017
+ throw new ParseError("starts_with requires 1 argument");
2018
+ }
2019
+ return str.startsWith(String(args[0]));
2020
+ }
2021
+ case "ends_with":
2022
+ case "endsWith": {
2023
+ if (args.length === 0) {
2024
+ throw new ParseError("ends_with requires 1 argument");
2025
+ }
2026
+ return str.endsWith(String(args[0]));
2027
+ }
2028
+ case "includes":
2029
+ case "contains": {
2030
+ if (args.length === 0) {
2031
+ throw new ParseError("includes requires 1 argument");
2032
+ }
2033
+ return str.includes(String(args[0]));
2034
+ }
2035
+ case "index_of":
2036
+ case "indexOf": {
2037
+ if (args.length === 0) {
2038
+ throw new ParseError("index_of requires 1 argument");
2039
+ }
2040
+ return str.indexOf(String(args[0]));
2041
+ }
2042
+ case "split": {
2043
+ if (args.length === 0) {
2044
+ throw new ParseError("split requires 1 argument");
2045
+ }
2046
+ return str.split(String(args[0]));
2047
+ }
2048
+ case "replace": {
2049
+ if (args.length < 2) {
2050
+ throw new ParseError("replace requires 2 arguments");
2051
+ }
2052
+ return str.replace(String(args[0]), String(args[1]));
2053
+ }
2054
+ case "replace_all":
2055
+ case "replaceAll": {
2056
+ if (args.length < 2) {
2057
+ throw new ParseError("replace_all requires 2 arguments");
2058
+ }
2059
+ const search = String(args[0]);
2060
+ const replace = String(args[1]);
2061
+ return str.split(search).join(replace);
2062
+ }
2063
+ case "pad_left":
2064
+ case "padStart": {
2065
+ if (args.length === 0) {
2066
+ throw new ParseError("pad_left requires at least 1 argument");
2067
+ }
2068
+ const length = this.toInt(args[0]);
2069
+ const fillString = args.length > 1 ? String(args[1]) : " ";
2070
+ return str.padStart(length, fillString);
2071
+ }
2072
+ case "pad_right":
2073
+ case "padEnd": {
2074
+ if (args.length === 0) {
2075
+ throw new ParseError("pad_right requires at least 1 argument");
2076
+ }
2077
+ const length = this.toInt(args[0]);
2078
+ const fillString = args.length > 1 ? String(args[1]) : " ";
2079
+ return str.padEnd(length, fillString);
2080
+ }
2081
+ default:
2082
+ throw new ParseError(`Unknown string method: ${method}`);
2083
+ }
2084
+ }
2085
+ /**
2086
+ * Evaluate array methods.
2087
+ * @private
2088
+ */
2089
+ evaluateArrayMethod(arr, method, args) {
2090
+ const array = Array.isArray(arr) ? arr : Array.from(arr);
2091
+ switch (method) {
2092
+ case "length":
2093
+ case "size":
2094
+ return array.length;
2095
+ case "first":
2096
+ return array[0];
2097
+ case "last":
2098
+ return array[array.length - 1];
2099
+ case "min":
2100
+ return Math.min(...array.map((v) => this.toNumber(v)));
2101
+ case "max":
2102
+ return Math.max(...array.map((v) => this.toNumber(v)));
2103
+ case "reverse":
2104
+ return [...array].reverse();
2105
+ case "sort":
2106
+ return [...array].sort((a, b) => this.compare(a, b));
2107
+ case "includes":
2108
+ case "contains": {
2109
+ if (args.length === 0) {
2110
+ throw new ParseError("includes requires 1 argument");
2111
+ }
2112
+ return array.some((item) => this.equals(item, args[0]));
2113
+ }
2114
+ case "index_of":
2115
+ case "indexOf": {
2116
+ if (args.length === 0) {
2117
+ throw new ParseError("index_of requires 1 argument");
2118
+ }
2119
+ return array.findIndex((item) => this.equals(item, args[0]));
2120
+ }
2121
+ case "slice": {
2122
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
2123
+ const end = args.length > 1 ? this.toInt(args[1]) : void 0;
2124
+ return array.slice(start, end);
2125
+ }
2126
+ default:
2127
+ throw new ParseError(`Unknown array method: ${method}`);
2128
+ }
2129
+ }
1811
2130
  /**
1812
2131
  * Evaluate enum access (EnumName::value).
1813
2132
  * @private
@@ -1837,6 +2156,38 @@ var Evaluator = class {
1837
2156
  const result = a % b;
1838
2157
  return result < 0 ? result + b : result;
1839
2158
  }
2159
+ /**
2160
+ * Helper: Bitwise operation with BigInt support.
2161
+ * JavaScript bitwise operators work on 32-bit integers, but Kaitai
2162
+ * may use 64-bit values. For values that fit in 32 bits, use native ops.
2163
+ * For larger values, convert to BigInt (with limitations).
2164
+ * @private
2165
+ */
2166
+ bitwiseOp(left, right, op) {
2167
+ if (typeof left === "bigint" || typeof right === "bigint") {
2168
+ const leftBig = typeof left === "bigint" ? left : BigInt(left);
2169
+ const rightBig = typeof right === "bigint" ? right : BigInt(right);
2170
+ if (op.toString().includes("<<")) {
2171
+ return leftBig << BigInt(Number(rightBig));
2172
+ }
2173
+ if (op.toString().includes(">>")) {
2174
+ return leftBig >> BigInt(Number(rightBig));
2175
+ }
2176
+ if (op.toString().includes("&")) {
2177
+ return leftBig & rightBig;
2178
+ }
2179
+ if (op.toString().includes("|")) {
2180
+ return leftBig | rightBig;
2181
+ }
2182
+ if (op.toString().includes("^")) {
2183
+ return leftBig ^ rightBig;
2184
+ }
2185
+ }
2186
+ if (left === void 0 || left === null || right === void 0 || right === null) {
2187
+ throw new ParseError("Cannot perform bitwise operation on null/undefined");
2188
+ }
2189
+ return op(this.toInt(left), this.toInt(right));
2190
+ }
1840
2191
  /**
1841
2192
  * Helper: Compare two values.
1842
2193
  * @private
@@ -1857,8 +2208,30 @@ var Evaluator = class {
1857
2208
  if (typeof left === "bigint" || typeof right === "bigint") {
1858
2209
  return BigInt(left) === BigInt(right);
1859
2210
  }
2211
+ const toArray = (v) => {
2212
+ if (Array.isArray(v))
2213
+ return v.map((x) => typeof x === "bigint" ? Number(x) : x);
2214
+ if (v instanceof Uint8Array) return Array.from(v);
2215
+ return null;
2216
+ };
2217
+ const leftArr = toArray(left);
2218
+ const rightArr = toArray(right);
2219
+ if (leftArr && rightArr) {
2220
+ if (leftArr.length !== rightArr.length) return false;
2221
+ for (let i = 0; i < leftArr.length; i++) {
2222
+ if (leftArr[i] !== rightArr[i]) return false;
2223
+ }
2224
+ return true;
2225
+ }
1860
2226
  return left === right;
1861
2227
  }
2228
+ /**
2229
+ * Evaluate an array literal.
2230
+ * @private
2231
+ */
2232
+ evaluateArrayLiteral(elements, context) {
2233
+ return elements.map((e) => this.evaluate(e, context));
2234
+ }
1862
2235
  /**
1863
2236
  * Helper: Convert to number.
1864
2237
  * @private
@@ -1893,7 +2266,8 @@ var Evaluator = class {
1893
2266
 
1894
2267
  // src/expression/index.ts
1895
2268
  function evaluateExpression(expression, context) {
1896
- const lexer = new Lexer(expression);
2269
+ const preprocessed = expression.replace(/\.as<[^>]+>/g, "");
2270
+ const lexer = new Lexer(preprocessed);
1897
2271
  const tokens = lexer.tokenize();
1898
2272
  const parser = new ExpressionParser(tokens);
1899
2273
  const ast = parser.parse();
@@ -1901,6 +2275,112 @@ function evaluateExpression(expression, context) {
1901
2275
  return evaluator.evaluate(ast, context);
1902
2276
  }
1903
2277
 
2278
+ // src/utils/process.ts
2279
+ import { inflate } from "pako";
2280
+ function applyProcess(data, process) {
2281
+ const spec = typeof process === "string" ? { algorithm: process } : process;
2282
+ const algorithm = spec.algorithm;
2283
+ if (!algorithm) {
2284
+ throw new ParseError("Process specification missing algorithm");
2285
+ }
2286
+ switch (algorithm) {
2287
+ case "zlib":
2288
+ return processZlib(data);
2289
+ case "xor":
2290
+ return processXor(data, spec.key);
2291
+ case "rol":
2292
+ return processRol(data, spec.amount, spec.group);
2293
+ case "ror":
2294
+ return processRor(data, spec.amount, spec.group);
2295
+ case "bswap2":
2296
+ return processByteswap(data, 2);
2297
+ case "bswap4":
2298
+ return processByteswap(data, 4);
2299
+ case "bswap8":
2300
+ return processByteswap(data, 8);
2301
+ case "bswap16":
2302
+ return processByteswap(data, 16);
2303
+ default:
2304
+ throw new ParseError(
2305
+ `Unknown process algorithm: ${algorithm}. Supported: zlib, xor, rol, ror, bswap2, bswap4, bswap8, bswap16`
2306
+ );
2307
+ }
2308
+ }
2309
+ function processZlib(data) {
2310
+ try {
2311
+ return inflate(data);
2312
+ } catch (error) {
2313
+ throw new ParseError(
2314
+ `Zlib decompression failed: ${error instanceof Error ? error.message : String(error)}`
2315
+ );
2316
+ }
2317
+ }
2318
+ function processXor(data, key) {
2319
+ if (key === void 0) {
2320
+ throw new ParseError("XOR process requires a key parameter");
2321
+ }
2322
+ const result = new Uint8Array(data.length);
2323
+ const keyBytes = Array.isArray(key) ? key : [key];
2324
+ if (keyBytes.length === 0) {
2325
+ throw new ParseError("XOR key cannot be empty");
2326
+ }
2327
+ for (let i = 0; i < data.length; i++) {
2328
+ result[i] = data[i] ^ keyBytes[i % keyBytes.length];
2329
+ }
2330
+ return result;
2331
+ }
2332
+ function processRol(data, amount, group) {
2333
+ const bits = amount ?? 1;
2334
+ const groupSize = group ?? 1;
2335
+ if (bits < 0 || bits > 7) {
2336
+ throw new ParseError("ROL amount must be between 0 and 7");
2337
+ }
2338
+ if (groupSize !== 1) {
2339
+ throw new ParseError("ROL with group size > 1 not yet supported");
2340
+ }
2341
+ const result = new Uint8Array(data.length);
2342
+ for (let i = 0; i < data.length; i++) {
2343
+ const byte = data[i];
2344
+ result[i] = (byte << bits | byte >> 8 - bits) & 255;
2345
+ }
2346
+ return result;
2347
+ }
2348
+ function processRor(data, amount, group) {
2349
+ const bits = amount ?? 1;
2350
+ const groupSize = group ?? 1;
2351
+ if (bits < 0 || bits > 7) {
2352
+ throw new ParseError("ROR amount must be between 0 and 7");
2353
+ }
2354
+ if (groupSize !== 1) {
2355
+ throw new ParseError("ROR with group size > 1 not yet supported");
2356
+ }
2357
+ const result = new Uint8Array(data.length);
2358
+ for (let i = 0; i < data.length; i++) {
2359
+ const byte = data[i];
2360
+ result[i] = (byte >> bits | byte << 8 - bits) & 255;
2361
+ }
2362
+ return result;
2363
+ }
2364
+ function processByteswap(data, groupSize) {
2365
+ if (![2, 4, 8, 16].includes(groupSize)) {
2366
+ throw new ParseError(
2367
+ `Invalid byteswap group size: ${groupSize}. Must be 2, 4, 8, or 16`
2368
+ );
2369
+ }
2370
+ if (data.length % groupSize !== 0) {
2371
+ throw new ParseError(
2372
+ `Data length ${data.length} is not aligned to group size ${groupSize}`
2373
+ );
2374
+ }
2375
+ const result = new Uint8Array(data.length);
2376
+ for (let i = 0; i < data.length; i += groupSize) {
2377
+ for (let j = 0; j < groupSize; j++) {
2378
+ result[i + j] = data[i + groupSize - 1 - j];
2379
+ }
2380
+ }
2381
+ return result;
2382
+ }
2383
+
1904
2384
  // src/interpreter/TypeInterpreter.ts
1905
2385
  var TypeInterpreter = class _TypeInterpreter {
1906
2386
  /**
@@ -1919,18 +2399,38 @@ var TypeInterpreter = class _TypeInterpreter {
1919
2399
  throw new ParseError("Root schema must have meta.id");
1920
2400
  }
1921
2401
  }
2402
+ /**
2403
+ * Safely extract a KaitaiStream from an object that may expose `_io`.
2404
+ * Avoids using `any` casts to satisfy linting.
2405
+ */
2406
+ static getKaitaiIO(val) {
2407
+ if (val && typeof val === "object") {
2408
+ const rec = val;
2409
+ const maybe = rec["_io"];
2410
+ if (maybe instanceof KaitaiStream) return maybe;
2411
+ }
2412
+ return null;
2413
+ }
1922
2414
  /**
1923
2415
  * Parse binary data according to the schema.
1924
2416
  *
1925
2417
  * @param stream - Binary stream to parse
1926
2418
  * @param parent - Parent object (for nested types)
1927
2419
  * @param typeArgs - Arguments for parametric types
2420
+ * @param root - Root object of the parse tree (for nested types)
1928
2421
  * @returns Parsed object
1929
2422
  */
1930
- parse(stream, parent, typeArgs) {
2423
+ parse(stream, parent, typeArgs, root) {
1931
2424
  const result = {};
1932
- const context = new Context(stream, result, parent, this.schema.enums);
2425
+ const actualRoot = root || result;
2426
+ const context = new Context(stream, actualRoot, parent, this.schema.enums);
1933
2427
  context.current = result;
2428
+ const startPos = stream.pos;
2429
+ result["_io"] = stream;
2430
+ if (root) {
2431
+ ;
2432
+ result["_root"] = root;
2433
+ }
1934
2434
  if (typeArgs && this.schema.params) {
1935
2435
  for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
1936
2436
  const param = this.schema.params[i];
@@ -1939,6 +2439,9 @@ var TypeInterpreter = class _TypeInterpreter {
1939
2439
  context.set(param.id, evaluatedArg);
1940
2440
  }
1941
2441
  }
2442
+ if (this.schema.instances) {
2443
+ this.setupInstances(result, stream, context);
2444
+ }
1942
2445
  if (this.schema.seq) {
1943
2446
  for (const attr of this.schema.seq) {
1944
2447
  const value = this.parseAttribute(attr, context);
@@ -1947,9 +2450,8 @@ var TypeInterpreter = class _TypeInterpreter {
1947
2450
  }
1948
2451
  }
1949
2452
  }
1950
- if (this.schema.instances) {
1951
- this.setupInstances(result, stream, context);
1952
- }
2453
+ const endPos = stream.pos;
2454
+ result["_sizeof"] = endPos - startPos;
1953
2455
  return result;
1954
2456
  }
1955
2457
  /**
@@ -2033,7 +2535,22 @@ var TypeInterpreter = class _TypeInterpreter {
2033
2535
  * @private
2034
2536
  */
2035
2537
  parseAttribute(attr, context) {
2036
- const stream = context.io;
2538
+ let stream = context.io;
2539
+ if (attr.io !== void 0) {
2540
+ const ioVal = this.evaluateValue(attr.io, context);
2541
+ if (ioVal instanceof KaitaiStream) {
2542
+ stream = ioVal;
2543
+ } else {
2544
+ const kio = _TypeInterpreter.getKaitaiIO(ioVal);
2545
+ if (kio) {
2546
+ stream = kio;
2547
+ } else {
2548
+ throw new ParseError(
2549
+ "io must evaluate to a KaitaiStream or an object with _io"
2550
+ );
2551
+ }
2552
+ }
2553
+ }
2037
2554
  if (attr.if) {
2038
2555
  const condition = this.evaluateValue(attr.if, context);
2039
2556
  if (!condition) {
@@ -2050,9 +2567,6 @@ var TypeInterpreter = class _TypeInterpreter {
2050
2567
  throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
2051
2568
  }
2052
2569
  }
2053
- if (attr.io) {
2054
- throw new NotImplementedError("Custom I/O streams");
2055
- }
2056
2570
  if (attr.repeat) {
2057
2571
  return this.parseRepeated(attr, context);
2058
2572
  }
@@ -2149,7 +2663,7 @@ var TypeInterpreter = class _TypeInterpreter {
2149
2663
  }
2150
2664
  return bytes;
2151
2665
  } else {
2152
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2666
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2153
2667
  const str = stream.readStr(expected.length, encoding);
2154
2668
  if (str !== expected) {
2155
2669
  throw new ValidationError(
@@ -2178,7 +2692,7 @@ var TypeInterpreter = class _TypeInterpreter {
2178
2692
  throw new ParseError(`size must be non-negative, got ${size}`);
2179
2693
  }
2180
2694
  if (type === "str" || !type) {
2181
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2695
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2182
2696
  let data;
2183
2697
  if (type === "str") {
2184
2698
  data = stream.readBytes(size);
@@ -2204,11 +2718,19 @@ var TypeInterpreter = class _TypeInterpreter {
2204
2718
  }
2205
2719
  if (attr["size-eos"]) {
2206
2720
  if (type === "str") {
2207
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2721
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2208
2722
  const bytes = stream.readBytesFull();
2209
2723
  return new TextDecoder(encoding).decode(bytes);
2210
2724
  } else {
2211
- return stream.readBytesFull();
2725
+ let bytes = stream.readBytesFull();
2726
+ if (attr.process) {
2727
+ bytes = this.applyProcessing(bytes, attr.process);
2728
+ }
2729
+ if (type) {
2730
+ const sub = new KaitaiStream(bytes);
2731
+ return this.parseType(type, sub, context, attr["type-args"]);
2732
+ }
2733
+ return bytes;
2212
2734
  }
2213
2735
  }
2214
2736
  if (!type) {
@@ -2234,11 +2756,13 @@ var TypeInterpreter = class _TypeInterpreter {
2234
2756
  context
2235
2757
  );
2236
2758
  }
2237
- if (isBuiltinType(type)) {
2238
- return this.parseBuiltinType(type, stream, context);
2759
+ const { typeName, args } = this.parseParameterizedType(type, context);
2760
+ const effectiveArgs = args.length > 0 ? args : typeArgs;
2761
+ if (isBuiltinType(typeName)) {
2762
+ return this.parseBuiltinType(typeName, stream, context);
2239
2763
  }
2240
- if (this.schema.types && type in this.schema.types) {
2241
- const typeSchema = this.schema.types[type];
2764
+ if (this.schema.types && typeName in this.schema.types) {
2765
+ const typeSchema = this.schema.types[typeName];
2242
2766
  const meta = this.schema.meta || this.parentMeta;
2243
2767
  if (this.schema.enums && !typeSchema.enums) {
2244
2768
  typeSchema.enums = this.schema.enums;
@@ -2247,9 +2771,100 @@ var TypeInterpreter = class _TypeInterpreter {
2247
2771
  typeSchema.types = this.schema.types;
2248
2772
  }
2249
2773
  const interpreter = new _TypeInterpreter(typeSchema, meta);
2250
- return interpreter.parse(stream, context.current, typeArgs);
2774
+ return interpreter.parse(
2775
+ stream,
2776
+ context.current,
2777
+ effectiveArgs,
2778
+ context.root
2779
+ );
2780
+ }
2781
+ throw new ParseError(`Unknown type: ${typeName}`);
2782
+ }
2783
+ /**
2784
+ * Parse parameterized type syntax and extract type name and arguments.
2785
+ * Supports: type_name(arg1, arg2, ...) or just type_name
2786
+ *
2787
+ * @param typeSpec - Type specification string
2788
+ * @param context - Execution context for evaluating argument expressions
2789
+ * @returns Object with typeName and evaluated args
2790
+ * @private
2791
+ */
2792
+ parseParameterizedType(typeSpec, context) {
2793
+ const match = typeSpec.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
2794
+ if (!match) {
2795
+ return { typeName: typeSpec, args: [] };
2796
+ }
2797
+ const typeName = match[1];
2798
+ const argsString = match[2].trim();
2799
+ if (!argsString) {
2800
+ return { typeName, args: [] };
2801
+ }
2802
+ const args = [];
2803
+ let current = "";
2804
+ let inString = false;
2805
+ let stringChar = "";
2806
+ let parenDepth = 0;
2807
+ for (let i = 0; i < argsString.length; i++) {
2808
+ const char = argsString[i];
2809
+ if (inString) {
2810
+ current += char;
2811
+ if (char === stringChar && argsString[i - 1] !== "\\") {
2812
+ inString = false;
2813
+ }
2814
+ } else if (char === '"' || char === "'") {
2815
+ inString = true;
2816
+ stringChar = char;
2817
+ current += char;
2818
+ } else if (char === "(") {
2819
+ parenDepth++;
2820
+ current += char;
2821
+ } else if (char === ")") {
2822
+ parenDepth--;
2823
+ current += char;
2824
+ } else if (char === "," && parenDepth === 0) {
2825
+ args.push(this.parseArgument(current.trim(), context));
2826
+ current = "";
2827
+ } else {
2828
+ current += char;
2829
+ }
2830
+ }
2831
+ if (current.trim()) {
2832
+ args.push(this.parseArgument(current.trim(), context));
2833
+ }
2834
+ return { typeName, args };
2835
+ }
2836
+ /**
2837
+ * Parse and evaluate a single type argument.
2838
+ *
2839
+ * @param arg - Argument string
2840
+ * @param context - Execution context
2841
+ * @returns Evaluated argument value
2842
+ * @private
2843
+ */
2844
+ parseArgument(arg, context) {
2845
+ if (arg === "true") return true;
2846
+ if (arg === "false") return false;
2847
+ if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
2848
+ return arg.slice(1, -1);
2849
+ }
2850
+ if (/^-?\d+$/.test(arg)) {
2851
+ return parseInt(arg, 10);
2852
+ }
2853
+ if (/^-?\d+\.\d+$/.test(arg)) {
2854
+ return parseFloat(arg);
2855
+ }
2856
+ if (/^0x[0-9a-f]+$/i.test(arg)) {
2857
+ return parseInt(arg, 16);
2858
+ }
2859
+ try {
2860
+ const result = this.evaluateValue(arg, context);
2861
+ if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
2862
+ return result;
2863
+ }
2864
+ return Number(result);
2865
+ } catch {
2866
+ return arg;
2251
2867
  }
2252
- throw new ParseError(`Unknown type: ${type}`);
2253
2868
  }
2254
2869
  /**
2255
2870
  * Parse a switch type (type selection based on expression).
@@ -2292,18 +2907,33 @@ var TypeInterpreter = class _TypeInterpreter {
2292
2907
  * @returns Parsed value
2293
2908
  * @private
2294
2909
  */
2295
- parseBuiltinType(type, stream, _context) {
2910
+ parseBuiltinType(type, stream, context) {
2296
2911
  const base = getBaseType(type);
2297
2912
  const typeEndian = getTypeEndianness(type);
2298
2913
  const meta = this.schema.meta || this.parentMeta;
2299
2914
  const metaEndian = meta?.endian;
2300
- const endian = typeEndian || (typeof metaEndian === "string" ? metaEndian : "le");
2915
+ let endian;
2916
+ if (typeEndian) {
2917
+ endian = typeEndian;
2918
+ } else if (typeof metaEndian === "string") {
2919
+ endian = metaEndian;
2920
+ } else if (metaEndian && typeof metaEndian === "object") {
2921
+ endian = this.evaluateEndianExpression(metaEndian, context);
2922
+ } else {
2923
+ endian = "le";
2924
+ }
2301
2925
  if (isIntegerType(type)) {
2302
2926
  return this.readInteger(base, endian, stream);
2303
2927
  }
2304
2928
  if (isFloatType(type)) {
2305
2929
  return this.readFloat(base, endian, stream);
2306
2930
  }
2931
+ if (/^b\d+$/.test(type)) {
2932
+ const n = parseInt(type.slice(1), 10);
2933
+ const val = stream.readBitsIntBe(n);
2934
+ const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
2935
+ return val <= maxSafe ? Number(val) : val;
2936
+ }
2307
2937
  if (isStringType(type)) {
2308
2938
  const encoding = this.schema.meta?.encoding || "UTF-8";
2309
2939
  if (type === "strz") {
@@ -2368,7 +2998,7 @@ var TypeInterpreter = class _TypeInterpreter {
2368
2998
  }
2369
2999
  /**
2370
3000
  * Apply processing transformation to data.
2371
- * Supports basic transformations like zlib decompression.
3001
+ * Delegates to the process utility module.
2372
3002
  *
2373
3003
  * @param data - Data to process
2374
3004
  * @param process - Processing specification
@@ -2376,13 +3006,35 @@ var TypeInterpreter = class _TypeInterpreter {
2376
3006
  * @private
2377
3007
  */
2378
3008
  applyProcessing(data, process) {
2379
- const processType = typeof process === "string" ? process : process.algorithm;
2380
- if (processType) {
2381
- throw new NotImplementedError(
2382
- `Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
2383
- );
3009
+ return applyProcess(data, process);
3010
+ }
3011
+ /**
3012
+ * Evaluate expression-based endianness (switch-on).
3013
+ *
3014
+ * @param endianExpr - Endianness expression with switch-on and cases
3015
+ * @param context - Execution context
3016
+ * @returns Resolved endianness ('le' or 'be')
3017
+ * @private
3018
+ */
3019
+ evaluateEndianExpression(endianExpr, context) {
3020
+ const switchOn = endianExpr["switch-on"];
3021
+ const cases = endianExpr.cases;
3022
+ if (!switchOn || typeof switchOn !== "string") {
3023
+ throw new ParseError('Endian expression missing "switch-on" field');
3024
+ }
3025
+ if (!cases || typeof cases !== "object") {
3026
+ throw new ParseError('Endian expression missing "cases" field');
3027
+ }
3028
+ const switchValue = this.evaluateValue(switchOn, context);
3029
+ const key = String(switchValue);
3030
+ if (key in cases) {
3031
+ const endian = cases[key];
3032
+ if (endian !== "le" && endian !== "be") {
3033
+ throw new ParseError(`Invalid endianness value: ${endian}`);
3034
+ }
3035
+ return endian;
2384
3036
  }
2385
- return data;
3037
+ return "le";
2386
3038
  }
2387
3039
  /**
2388
3040
  * Evaluate a value that can be an expression or literal.
@@ -2524,6 +3176,12 @@ export {
2524
3176
  * @author Fabiano Pinto
2525
3177
  * @license MIT
2526
3178
  */
3179
+ /**
3180
+ * @fileoverview Data processing utilities for Kaitai Struct
3181
+ * @module utils/process
3182
+ * @author Fabiano Pinto
3183
+ * @license MIT
3184
+ */
2527
3185
  /**
2528
3186
  * @fileoverview Type interpreter for executing Kaitai Struct schemas
2529
3187
  * @module interpreter/TypeInterpreter