@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.js CHANGED
@@ -42,30 +42,68 @@ module.exports = __toCommonJS(index_exports);
42
42
 
43
43
  // src/utils/errors.ts
44
44
  var KaitaiError = class _KaitaiError extends Error {
45
- constructor(message, position) {
46
- super(message);
45
+ constructor(message, position, context) {
46
+ super(_KaitaiError.formatMessage(message, position, context));
47
47
  this.position = position;
48
+ this.context = context;
48
49
  this.name = "KaitaiError";
49
50
  Object.setPrototypeOf(this, _KaitaiError.prototype);
50
51
  }
52
+ /**
53
+ * Format error message with position and context.
54
+ * @private
55
+ */
56
+ static formatMessage(message, position, context) {
57
+ let formatted = message;
58
+ if (position !== void 0) {
59
+ formatted += ` (at byte offset 0x${position.toString(16).toUpperCase()})`;
60
+ }
61
+ if (context && context.length > 0) {
62
+ const hexContext = _KaitaiError.formatHexContext(context, position);
63
+ formatted += `
64
+ ${hexContext}`;
65
+ }
66
+ return formatted;
67
+ }
68
+ /**
69
+ * Format hex dump context around error position.
70
+ * @private
71
+ */
72
+ static formatHexContext(data, position) {
73
+ const contextSize = 16;
74
+ const start = Math.max(0, (position ?? 0) - contextSize);
75
+ const end = Math.min(data.length, (position ?? 0) + contextSize);
76
+ const chunk = data.slice(start, end);
77
+ const lines = ["Context:"];
78
+ let offset = start;
79
+ for (let i = 0; i < chunk.length; i += 16) {
80
+ const lineBytes = chunk.slice(i, i + 16);
81
+ const hex = Array.from(lineBytes).map((b) => b.toString(16).padStart(2, "0")).join(" ");
82
+ const ascii = Array.from(lineBytes).map((b) => b >= 32 && b <= 126 ? String.fromCharCode(b) : ".").join("");
83
+ const offsetStr = ` ${(offset + i).toString(16).padStart(8, "0")}`;
84
+ const marker = position !== void 0 && position >= offset + i && position < offset + i + lineBytes.length ? " <--" : "";
85
+ lines.push(`${offsetStr}: ${hex.padEnd(48, " ")} | ${ascii}${marker}`);
86
+ }
87
+ return lines.join("\n");
88
+ }
51
89
  };
52
90
  var ValidationError = class _ValidationError extends KaitaiError {
53
- constructor(message, position) {
54
- super(message, position);
91
+ constructor(message, position, context) {
92
+ super(message, position, context);
55
93
  this.name = "ValidationError";
56
94
  Object.setPrototypeOf(this, _ValidationError.prototype);
57
95
  }
58
96
  };
59
97
  var ParseError = class _ParseError extends KaitaiError {
60
- constructor(message, position) {
61
- super(message, position);
98
+ constructor(message, position, context) {
99
+ super(message, position, context);
62
100
  this.name = "ParseError";
63
101
  Object.setPrototypeOf(this, _ParseError.prototype);
64
102
  }
65
103
  };
66
104
  var EOFError = class _EOFError extends KaitaiError {
67
- constructor(message = "Unexpected end of stream", position) {
68
- super(message, position);
105
+ constructor(message = "Unexpected end of stream", position, context) {
106
+ super(message, position, context);
69
107
  this.name = "EOFError";
70
108
  Object.setPrototypeOf(this, _EOFError.prototype);
71
109
  }
@@ -591,7 +629,9 @@ var BUILTIN_TYPES = [
591
629
  "strz"
592
630
  ];
593
631
  function isBuiltinType(type) {
594
- return BUILTIN_TYPES.includes(type);
632
+ if (BUILTIN_TYPES.includes(type)) return true;
633
+ if (/^b\d+$/.test(type)) return true;
634
+ return false;
595
635
  }
596
636
  function getTypeEndianness(type) {
597
637
  if (type.endsWith("le")) return "le";
@@ -851,11 +891,83 @@ var KsyParser = class {
851
891
  * @param imports - Map of import names to their YAML content
852
892
  * @param options - Parsing options
853
893
  * @returns Parsed schema with resolved imports
854
- */
855
- parseWithImports(mainYaml, _imports, options = {}) {
894
+ * @throws {ParseError} If import resolution fails
895
+ * @example
896
+ * ```typescript
897
+ * const parser = new KsyParser()
898
+ * const imports = new Map([
899
+ * ['/common/riff', riffYamlContent]
900
+ * ])
901
+ * const schema = parser.parseWithImports(wavYaml, imports)
902
+ * ```
903
+ */
904
+ parseWithImports(mainYaml, imports, options = {}) {
856
905
  const mainSchema = this.parse(mainYaml, options);
906
+ if (!mainSchema.meta.imports || mainSchema.meta.imports.length === 0) {
907
+ return mainSchema;
908
+ }
909
+ const resolvedTypes = {};
910
+ for (const importPath of mainSchema.meta.imports) {
911
+ if (!imports.has(importPath)) {
912
+ throw new ParseError(
913
+ `Import not found: ${importPath}. Available imports: ${Array.from(imports.keys()).join(", ")}`
914
+ );
915
+ }
916
+ const importYaml = imports.get(importPath);
917
+ const importedSchema = this.parse(importYaml, {
918
+ ...options,
919
+ validate: false
920
+ // Skip validation for imported schemas
921
+ });
922
+ const namespace = this.extractNamespace(importPath);
923
+ if (importedSchema.types) {
924
+ for (const [typeName, typeSchema] of Object.entries(
925
+ importedSchema.types
926
+ )) {
927
+ const qualifiedName = `${namespace}::${typeName}`;
928
+ resolvedTypes[qualifiedName] = typeSchema;
929
+ }
930
+ }
931
+ resolvedTypes[namespace] = {
932
+ meta: importedSchema.meta,
933
+ seq: importedSchema.seq,
934
+ instances: importedSchema.instances,
935
+ types: importedSchema.types,
936
+ enums: importedSchema.enums
937
+ };
938
+ if (importedSchema.enums) {
939
+ if (!mainSchema.enums) {
940
+ mainSchema.enums = {};
941
+ }
942
+ for (const [enumName, enumSpec] of Object.entries(
943
+ importedSchema.enums
944
+ )) {
945
+ const qualifiedEnumName = `${namespace}::${enumName}`;
946
+ mainSchema.enums[qualifiedEnumName] = enumSpec;
947
+ }
948
+ }
949
+ }
950
+ if (Object.keys(resolvedTypes).length > 0) {
951
+ mainSchema.types = {
952
+ ...resolvedTypes,
953
+ ...mainSchema.types
954
+ };
955
+ }
857
956
  return mainSchema;
858
957
  }
958
+ /**
959
+ * Extract namespace from import path.
960
+ * Converts paths like '/common/riff' or 'common/riff' to 'riff'.
961
+ *
962
+ * @param importPath - Import path from meta.imports
963
+ * @returns Namespace identifier
964
+ * @private
965
+ */
966
+ extractNamespace(importPath) {
967
+ const normalized = importPath.startsWith("/") ? importPath.slice(1) : importPath;
968
+ const segments = normalized.split("/");
969
+ return segments[segments.length - 1];
970
+ }
859
971
  };
860
972
 
861
973
  // src/interpreter/Context.ts
@@ -1166,6 +1278,17 @@ var Lexer = class {
1166
1278
  }
1167
1279
  return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
1168
1280
  }
1281
+ if (this.current === "0" && this.peek() === "b") {
1282
+ value += this.current;
1283
+ this.advance();
1284
+ value += this.current;
1285
+ this.advance();
1286
+ while (this.current !== null && /[01]/.test(this.current)) {
1287
+ value += this.current;
1288
+ this.advance();
1289
+ }
1290
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 2), start);
1291
+ }
1169
1292
  while (this.current !== null && this.isDigit(this.current)) {
1170
1293
  value += this.current;
1171
1294
  this.advance();
@@ -1377,6 +1500,9 @@ function createMethodCall(object, method, args) {
1377
1500
  function createEnumAccess(enumName, value) {
1378
1501
  return { kind: "EnumAccess", enumName, value };
1379
1502
  }
1503
+ function createArrayLiteral(elements) {
1504
+ return { kind: "ArrayLiteral", elements };
1505
+ }
1380
1506
 
1381
1507
  // src/expression/Parser.ts
1382
1508
  var ExpressionParser = class {
@@ -1673,6 +1799,17 @@ var ExpressionParser = class {
1673
1799
  this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
1674
1800
  return expr;
1675
1801
  }
1802
+ if (this.match("LBRACKET" /* LBRACKET */)) {
1803
+ const elements = [];
1804
+ if (this.current().type !== "RBRACKET" /* RBRACKET */) {
1805
+ elements.push(this.parseTernary());
1806
+ while (this.match("COMMA" /* COMMA */)) {
1807
+ elements.push(this.parseTernary());
1808
+ }
1809
+ }
1810
+ this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array literal");
1811
+ return createArrayLiteral(elements);
1812
+ }
1676
1813
  throw new ParseError(
1677
1814
  `Unexpected token: ${this.current().type}`,
1678
1815
  this.current().position
@@ -1711,6 +1848,8 @@ var Evaluator = class {
1711
1848
  return this.evaluateMethodCall(n.object, n.method, n.args, context);
1712
1849
  case "EnumAccess":
1713
1850
  return this.evaluateEnumAccess(n.enumName, n.value, context);
1851
+ case "ArrayLiteral":
1852
+ return this.evaluateArrayLiteral(n.elements, context);
1714
1853
  default:
1715
1854
  throw new ParseError(`Unknown AST node kind: ${node.kind}`);
1716
1855
  }
@@ -1756,15 +1895,15 @@ var Evaluator = class {
1756
1895
  return !this.equals(leftVal, rightVal);
1757
1896
  // Bitwise
1758
1897
  case "<<":
1759
- return this.toInt(leftVal) << this.toInt(rightVal);
1898
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a << b);
1760
1899
  case ">>":
1761
- return this.toInt(leftVal) >> this.toInt(rightVal);
1900
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a >> b);
1762
1901
  case "&":
1763
- return this.toInt(leftVal) & this.toInt(rightVal);
1902
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a & b);
1764
1903
  case "|":
1765
- return this.toInt(leftVal) | this.toInt(rightVal);
1904
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a | b);
1766
1905
  case "^":
1767
- return this.toInt(leftVal) ^ this.toInt(rightVal);
1906
+ return this.bitwiseOp(leftVal, rightVal, (a, b) => a ^ b);
1768
1907
  // Logical
1769
1908
  case "and":
1770
1909
  return this.toBoolean(leftVal) && this.toBoolean(rightVal);
@@ -1808,6 +1947,16 @@ var Evaluator = class {
1808
1947
  `Cannot access property ${property} of null/undefined`
1809
1948
  );
1810
1949
  }
1950
+ if (property === "to_i") {
1951
+ if (typeof obj === "number") return Math.floor(obj);
1952
+ if (typeof obj === "bigint") return Number(obj);
1953
+ if (typeof obj === "string") return parseInt(obj, 10);
1954
+ if (typeof obj === "boolean") return obj ? 1 : 0;
1955
+ return this.toInt(obj);
1956
+ }
1957
+ if (property === "to_s") {
1958
+ return String(obj);
1959
+ }
1811
1960
  if (typeof obj === "object") {
1812
1961
  return obj[property];
1813
1962
  }
@@ -1834,8 +1983,9 @@ var Evaluator = class {
1834
1983
  * Evaluate method call (object.method()).
1835
1984
  * @private
1836
1985
  */
1837
- evaluateMethodCall(object, method, _args, context) {
1986
+ evaluateMethodCall(object, method, args, context) {
1838
1987
  const obj = this.evaluate(object, context);
1988
+ const evalArgs = args.map((arg) => this.evaluate(arg, context));
1839
1989
  if (method === "length" || method === "size") {
1840
1990
  if (Array.isArray(obj)) return obj.length;
1841
1991
  if (obj instanceof Uint8Array) return obj.length;
@@ -1843,13 +1993,182 @@ var Evaluator = class {
1843
1993
  throw new ParseError(`Object does not have a ${method} property`);
1844
1994
  }
1845
1995
  if (method === "to_i") {
1996
+ const base = evalArgs.length > 0 ? this.toInt(evalArgs[0]) : 10;
1997
+ if (typeof obj === "string") {
1998
+ return parseInt(obj, base);
1999
+ }
1846
2000
  return this.toInt(obj);
1847
2001
  }
1848
2002
  if (method === "to_s") {
1849
2003
  return String(obj);
1850
2004
  }
2005
+ if (typeof obj === "string") {
2006
+ return this.evaluateStringMethod(obj, method, evalArgs);
2007
+ }
2008
+ if (Array.isArray(obj) || obj instanceof Uint8Array) {
2009
+ return this.evaluateArrayMethod(obj, method, evalArgs);
2010
+ }
1851
2011
  throw new ParseError(`Unknown method: ${method}`);
1852
2012
  }
2013
+ /**
2014
+ * Evaluate string methods.
2015
+ * @private
2016
+ */
2017
+ evaluateStringMethod(str, method, args) {
2018
+ switch (method) {
2019
+ case "substring": {
2020
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
2021
+ const end = args.length > 1 ? this.toInt(args[1]) : void 0;
2022
+ return str.substring(start, end);
2023
+ }
2024
+ case "substr": {
2025
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
2026
+ const length = args.length > 1 ? this.toInt(args[1]) : void 0;
2027
+ return str.substr(start, length);
2028
+ }
2029
+ case "reverse":
2030
+ return str.split("").reverse().join("");
2031
+ case "to_i": {
2032
+ const base = args.length > 0 ? this.toInt(args[0]) : 10;
2033
+ return parseInt(str, base);
2034
+ }
2035
+ case "length":
2036
+ case "size":
2037
+ return str.length;
2038
+ // Ruby-style string methods used in Kaitai
2039
+ case "upcase":
2040
+ case "to_upper":
2041
+ return str.toUpperCase();
2042
+ case "downcase":
2043
+ case "to_lower":
2044
+ return str.toLowerCase();
2045
+ case "capitalize":
2046
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
2047
+ case "strip":
2048
+ case "trim":
2049
+ return str.trim();
2050
+ case "lstrip":
2051
+ case "trim_start":
2052
+ return str.trimStart();
2053
+ case "rstrip":
2054
+ case "trim_end":
2055
+ return str.trimEnd();
2056
+ case "starts_with":
2057
+ case "startsWith": {
2058
+ if (args.length === 0) {
2059
+ throw new ParseError("starts_with requires 1 argument");
2060
+ }
2061
+ return str.startsWith(String(args[0]));
2062
+ }
2063
+ case "ends_with":
2064
+ case "endsWith": {
2065
+ if (args.length === 0) {
2066
+ throw new ParseError("ends_with requires 1 argument");
2067
+ }
2068
+ return str.endsWith(String(args[0]));
2069
+ }
2070
+ case "includes":
2071
+ case "contains": {
2072
+ if (args.length === 0) {
2073
+ throw new ParseError("includes requires 1 argument");
2074
+ }
2075
+ return str.includes(String(args[0]));
2076
+ }
2077
+ case "index_of":
2078
+ case "indexOf": {
2079
+ if (args.length === 0) {
2080
+ throw new ParseError("index_of requires 1 argument");
2081
+ }
2082
+ return str.indexOf(String(args[0]));
2083
+ }
2084
+ case "split": {
2085
+ if (args.length === 0) {
2086
+ throw new ParseError("split requires 1 argument");
2087
+ }
2088
+ return str.split(String(args[0]));
2089
+ }
2090
+ case "replace": {
2091
+ if (args.length < 2) {
2092
+ throw new ParseError("replace requires 2 arguments");
2093
+ }
2094
+ return str.replace(String(args[0]), String(args[1]));
2095
+ }
2096
+ case "replace_all":
2097
+ case "replaceAll": {
2098
+ if (args.length < 2) {
2099
+ throw new ParseError("replace_all requires 2 arguments");
2100
+ }
2101
+ const search = String(args[0]);
2102
+ const replace = String(args[1]);
2103
+ return str.split(search).join(replace);
2104
+ }
2105
+ case "pad_left":
2106
+ case "padStart": {
2107
+ if (args.length === 0) {
2108
+ throw new ParseError("pad_left requires at least 1 argument");
2109
+ }
2110
+ const length = this.toInt(args[0]);
2111
+ const fillString = args.length > 1 ? String(args[1]) : " ";
2112
+ return str.padStart(length, fillString);
2113
+ }
2114
+ case "pad_right":
2115
+ case "padEnd": {
2116
+ if (args.length === 0) {
2117
+ throw new ParseError("pad_right requires at least 1 argument");
2118
+ }
2119
+ const length = this.toInt(args[0]);
2120
+ const fillString = args.length > 1 ? String(args[1]) : " ";
2121
+ return str.padEnd(length, fillString);
2122
+ }
2123
+ default:
2124
+ throw new ParseError(`Unknown string method: ${method}`);
2125
+ }
2126
+ }
2127
+ /**
2128
+ * Evaluate array methods.
2129
+ * @private
2130
+ */
2131
+ evaluateArrayMethod(arr, method, args) {
2132
+ const array = Array.isArray(arr) ? arr : Array.from(arr);
2133
+ switch (method) {
2134
+ case "length":
2135
+ case "size":
2136
+ return array.length;
2137
+ case "first":
2138
+ return array[0];
2139
+ case "last":
2140
+ return array[array.length - 1];
2141
+ case "min":
2142
+ return Math.min(...array.map((v) => this.toNumber(v)));
2143
+ case "max":
2144
+ return Math.max(...array.map((v) => this.toNumber(v)));
2145
+ case "reverse":
2146
+ return [...array].reverse();
2147
+ case "sort":
2148
+ return [...array].sort((a, b) => this.compare(a, b));
2149
+ case "includes":
2150
+ case "contains": {
2151
+ if (args.length === 0) {
2152
+ throw new ParseError("includes requires 1 argument");
2153
+ }
2154
+ return array.some((item) => this.equals(item, args[0]));
2155
+ }
2156
+ case "index_of":
2157
+ case "indexOf": {
2158
+ if (args.length === 0) {
2159
+ throw new ParseError("index_of requires 1 argument");
2160
+ }
2161
+ return array.findIndex((item) => this.equals(item, args[0]));
2162
+ }
2163
+ case "slice": {
2164
+ const start = args.length > 0 ? this.toInt(args[0]) : 0;
2165
+ const end = args.length > 1 ? this.toInt(args[1]) : void 0;
2166
+ return array.slice(start, end);
2167
+ }
2168
+ default:
2169
+ throw new ParseError(`Unknown array method: ${method}`);
2170
+ }
2171
+ }
1853
2172
  /**
1854
2173
  * Evaluate enum access (EnumName::value).
1855
2174
  * @private
@@ -1879,6 +2198,38 @@ var Evaluator = class {
1879
2198
  const result = a % b;
1880
2199
  return result < 0 ? result + b : result;
1881
2200
  }
2201
+ /**
2202
+ * Helper: Bitwise operation with BigInt support.
2203
+ * JavaScript bitwise operators work on 32-bit integers, but Kaitai
2204
+ * may use 64-bit values. For values that fit in 32 bits, use native ops.
2205
+ * For larger values, convert to BigInt (with limitations).
2206
+ * @private
2207
+ */
2208
+ bitwiseOp(left, right, op) {
2209
+ if (typeof left === "bigint" || typeof right === "bigint") {
2210
+ const leftBig = typeof left === "bigint" ? left : BigInt(left);
2211
+ const rightBig = typeof right === "bigint" ? right : BigInt(right);
2212
+ if (op.toString().includes("<<")) {
2213
+ return leftBig << BigInt(Number(rightBig));
2214
+ }
2215
+ if (op.toString().includes(">>")) {
2216
+ return leftBig >> BigInt(Number(rightBig));
2217
+ }
2218
+ if (op.toString().includes("&")) {
2219
+ return leftBig & rightBig;
2220
+ }
2221
+ if (op.toString().includes("|")) {
2222
+ return leftBig | rightBig;
2223
+ }
2224
+ if (op.toString().includes("^")) {
2225
+ return leftBig ^ rightBig;
2226
+ }
2227
+ }
2228
+ if (left === void 0 || left === null || right === void 0 || right === null) {
2229
+ throw new ParseError("Cannot perform bitwise operation on null/undefined");
2230
+ }
2231
+ return op(this.toInt(left), this.toInt(right));
2232
+ }
1882
2233
  /**
1883
2234
  * Helper: Compare two values.
1884
2235
  * @private
@@ -1899,8 +2250,30 @@ var Evaluator = class {
1899
2250
  if (typeof left === "bigint" || typeof right === "bigint") {
1900
2251
  return BigInt(left) === BigInt(right);
1901
2252
  }
2253
+ const toArray = (v) => {
2254
+ if (Array.isArray(v))
2255
+ return v.map((x) => typeof x === "bigint" ? Number(x) : x);
2256
+ if (v instanceof Uint8Array) return Array.from(v);
2257
+ return null;
2258
+ };
2259
+ const leftArr = toArray(left);
2260
+ const rightArr = toArray(right);
2261
+ if (leftArr && rightArr) {
2262
+ if (leftArr.length !== rightArr.length) return false;
2263
+ for (let i = 0; i < leftArr.length; i++) {
2264
+ if (leftArr[i] !== rightArr[i]) return false;
2265
+ }
2266
+ return true;
2267
+ }
1902
2268
  return left === right;
1903
2269
  }
2270
+ /**
2271
+ * Evaluate an array literal.
2272
+ * @private
2273
+ */
2274
+ evaluateArrayLiteral(elements, context) {
2275
+ return elements.map((e) => this.evaluate(e, context));
2276
+ }
1904
2277
  /**
1905
2278
  * Helper: Convert to number.
1906
2279
  * @private
@@ -1935,7 +2308,8 @@ var Evaluator = class {
1935
2308
 
1936
2309
  // src/expression/index.ts
1937
2310
  function evaluateExpression(expression, context) {
1938
- const lexer = new Lexer(expression);
2311
+ const preprocessed = expression.replace(/\.as<[^>]+>/g, "");
2312
+ const lexer = new Lexer(preprocessed);
1939
2313
  const tokens = lexer.tokenize();
1940
2314
  const parser = new ExpressionParser(tokens);
1941
2315
  const ast = parser.parse();
@@ -1943,6 +2317,112 @@ function evaluateExpression(expression, context) {
1943
2317
  return evaluator.evaluate(ast, context);
1944
2318
  }
1945
2319
 
2320
+ // src/utils/process.ts
2321
+ var import_pako = require("pako");
2322
+ function applyProcess(data, process) {
2323
+ const spec = typeof process === "string" ? { algorithm: process } : process;
2324
+ const algorithm = spec.algorithm;
2325
+ if (!algorithm) {
2326
+ throw new ParseError("Process specification missing algorithm");
2327
+ }
2328
+ switch (algorithm) {
2329
+ case "zlib":
2330
+ return processZlib(data);
2331
+ case "xor":
2332
+ return processXor(data, spec.key);
2333
+ case "rol":
2334
+ return processRol(data, spec.amount, spec.group);
2335
+ case "ror":
2336
+ return processRor(data, spec.amount, spec.group);
2337
+ case "bswap2":
2338
+ return processByteswap(data, 2);
2339
+ case "bswap4":
2340
+ return processByteswap(data, 4);
2341
+ case "bswap8":
2342
+ return processByteswap(data, 8);
2343
+ case "bswap16":
2344
+ return processByteswap(data, 16);
2345
+ default:
2346
+ throw new ParseError(
2347
+ `Unknown process algorithm: ${algorithm}. Supported: zlib, xor, rol, ror, bswap2, bswap4, bswap8, bswap16`
2348
+ );
2349
+ }
2350
+ }
2351
+ function processZlib(data) {
2352
+ try {
2353
+ return (0, import_pako.inflate)(data);
2354
+ } catch (error) {
2355
+ throw new ParseError(
2356
+ `Zlib decompression failed: ${error instanceof Error ? error.message : String(error)}`
2357
+ );
2358
+ }
2359
+ }
2360
+ function processXor(data, key) {
2361
+ if (key === void 0) {
2362
+ throw new ParseError("XOR process requires a key parameter");
2363
+ }
2364
+ const result = new Uint8Array(data.length);
2365
+ const keyBytes = Array.isArray(key) ? key : [key];
2366
+ if (keyBytes.length === 0) {
2367
+ throw new ParseError("XOR key cannot be empty");
2368
+ }
2369
+ for (let i = 0; i < data.length; i++) {
2370
+ result[i] = data[i] ^ keyBytes[i % keyBytes.length];
2371
+ }
2372
+ return result;
2373
+ }
2374
+ function processRol(data, amount, group) {
2375
+ const bits = amount ?? 1;
2376
+ const groupSize = group ?? 1;
2377
+ if (bits < 0 || bits > 7) {
2378
+ throw new ParseError("ROL amount must be between 0 and 7");
2379
+ }
2380
+ if (groupSize !== 1) {
2381
+ throw new ParseError("ROL with group size > 1 not yet supported");
2382
+ }
2383
+ const result = new Uint8Array(data.length);
2384
+ for (let i = 0; i < data.length; i++) {
2385
+ const byte = data[i];
2386
+ result[i] = (byte << bits | byte >> 8 - bits) & 255;
2387
+ }
2388
+ return result;
2389
+ }
2390
+ function processRor(data, amount, group) {
2391
+ const bits = amount ?? 1;
2392
+ const groupSize = group ?? 1;
2393
+ if (bits < 0 || bits > 7) {
2394
+ throw new ParseError("ROR amount must be between 0 and 7");
2395
+ }
2396
+ if (groupSize !== 1) {
2397
+ throw new ParseError("ROR with group size > 1 not yet supported");
2398
+ }
2399
+ const result = new Uint8Array(data.length);
2400
+ for (let i = 0; i < data.length; i++) {
2401
+ const byte = data[i];
2402
+ result[i] = (byte >> bits | byte << 8 - bits) & 255;
2403
+ }
2404
+ return result;
2405
+ }
2406
+ function processByteswap(data, groupSize) {
2407
+ if (![2, 4, 8, 16].includes(groupSize)) {
2408
+ throw new ParseError(
2409
+ `Invalid byteswap group size: ${groupSize}. Must be 2, 4, 8, or 16`
2410
+ );
2411
+ }
2412
+ if (data.length % groupSize !== 0) {
2413
+ throw new ParseError(
2414
+ `Data length ${data.length} is not aligned to group size ${groupSize}`
2415
+ );
2416
+ }
2417
+ const result = new Uint8Array(data.length);
2418
+ for (let i = 0; i < data.length; i += groupSize) {
2419
+ for (let j = 0; j < groupSize; j++) {
2420
+ result[i + j] = data[i + groupSize - 1 - j];
2421
+ }
2422
+ }
2423
+ return result;
2424
+ }
2425
+
1946
2426
  // src/interpreter/TypeInterpreter.ts
1947
2427
  var TypeInterpreter = class _TypeInterpreter {
1948
2428
  /**
@@ -1961,18 +2441,38 @@ var TypeInterpreter = class _TypeInterpreter {
1961
2441
  throw new ParseError("Root schema must have meta.id");
1962
2442
  }
1963
2443
  }
2444
+ /**
2445
+ * Safely extract a KaitaiStream from an object that may expose `_io`.
2446
+ * Avoids using `any` casts to satisfy linting.
2447
+ */
2448
+ static getKaitaiIO(val) {
2449
+ if (val && typeof val === "object") {
2450
+ const rec = val;
2451
+ const maybe = rec["_io"];
2452
+ if (maybe instanceof KaitaiStream) return maybe;
2453
+ }
2454
+ return null;
2455
+ }
1964
2456
  /**
1965
2457
  * Parse binary data according to the schema.
1966
2458
  *
1967
2459
  * @param stream - Binary stream to parse
1968
2460
  * @param parent - Parent object (for nested types)
1969
2461
  * @param typeArgs - Arguments for parametric types
2462
+ * @param root - Root object of the parse tree (for nested types)
1970
2463
  * @returns Parsed object
1971
2464
  */
1972
- parse(stream, parent, typeArgs) {
2465
+ parse(stream, parent, typeArgs, root) {
1973
2466
  const result = {};
1974
- const context = new Context(stream, result, parent, this.schema.enums);
2467
+ const actualRoot = root || result;
2468
+ const context = new Context(stream, actualRoot, parent, this.schema.enums);
1975
2469
  context.current = result;
2470
+ const startPos = stream.pos;
2471
+ result["_io"] = stream;
2472
+ if (root) {
2473
+ ;
2474
+ result["_root"] = root;
2475
+ }
1976
2476
  if (typeArgs && this.schema.params) {
1977
2477
  for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
1978
2478
  const param = this.schema.params[i];
@@ -1981,6 +2481,9 @@ var TypeInterpreter = class _TypeInterpreter {
1981
2481
  context.set(param.id, evaluatedArg);
1982
2482
  }
1983
2483
  }
2484
+ if (this.schema.instances) {
2485
+ this.setupInstances(result, stream, context);
2486
+ }
1984
2487
  if (this.schema.seq) {
1985
2488
  for (const attr of this.schema.seq) {
1986
2489
  const value = this.parseAttribute(attr, context);
@@ -1989,9 +2492,8 @@ var TypeInterpreter = class _TypeInterpreter {
1989
2492
  }
1990
2493
  }
1991
2494
  }
1992
- if (this.schema.instances) {
1993
- this.setupInstances(result, stream, context);
1994
- }
2495
+ const endPos = stream.pos;
2496
+ result["_sizeof"] = endPos - startPos;
1995
2497
  return result;
1996
2498
  }
1997
2499
  /**
@@ -2075,7 +2577,22 @@ var TypeInterpreter = class _TypeInterpreter {
2075
2577
  * @private
2076
2578
  */
2077
2579
  parseAttribute(attr, context) {
2078
- const stream = context.io;
2580
+ let stream = context.io;
2581
+ if (attr.io !== void 0) {
2582
+ const ioVal = this.evaluateValue(attr.io, context);
2583
+ if (ioVal instanceof KaitaiStream) {
2584
+ stream = ioVal;
2585
+ } else {
2586
+ const kio = _TypeInterpreter.getKaitaiIO(ioVal);
2587
+ if (kio) {
2588
+ stream = kio;
2589
+ } else {
2590
+ throw new ParseError(
2591
+ "io must evaluate to a KaitaiStream or an object with _io"
2592
+ );
2593
+ }
2594
+ }
2595
+ }
2079
2596
  if (attr.if) {
2080
2597
  const condition = this.evaluateValue(attr.if, context);
2081
2598
  if (!condition) {
@@ -2092,9 +2609,6 @@ var TypeInterpreter = class _TypeInterpreter {
2092
2609
  throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
2093
2610
  }
2094
2611
  }
2095
- if (attr.io) {
2096
- throw new NotImplementedError("Custom I/O streams");
2097
- }
2098
2612
  if (attr.repeat) {
2099
2613
  return this.parseRepeated(attr, context);
2100
2614
  }
@@ -2191,7 +2705,7 @@ var TypeInterpreter = class _TypeInterpreter {
2191
2705
  }
2192
2706
  return bytes;
2193
2707
  } else {
2194
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2708
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2195
2709
  const str = stream.readStr(expected.length, encoding);
2196
2710
  if (str !== expected) {
2197
2711
  throw new ValidationError(
@@ -2220,7 +2734,7 @@ var TypeInterpreter = class _TypeInterpreter {
2220
2734
  throw new ParseError(`size must be non-negative, got ${size}`);
2221
2735
  }
2222
2736
  if (type === "str" || !type) {
2223
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2737
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2224
2738
  let data;
2225
2739
  if (type === "str") {
2226
2740
  data = stream.readBytes(size);
@@ -2246,11 +2760,19 @@ var TypeInterpreter = class _TypeInterpreter {
2246
2760
  }
2247
2761
  if (attr["size-eos"]) {
2248
2762
  if (type === "str") {
2249
- const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
2763
+ const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
2250
2764
  const bytes = stream.readBytesFull();
2251
2765
  return new TextDecoder(encoding).decode(bytes);
2252
2766
  } else {
2253
- return stream.readBytesFull();
2767
+ let bytes = stream.readBytesFull();
2768
+ if (attr.process) {
2769
+ bytes = this.applyProcessing(bytes, attr.process);
2770
+ }
2771
+ if (type) {
2772
+ const sub = new KaitaiStream(bytes);
2773
+ return this.parseType(type, sub, context, attr["type-args"]);
2774
+ }
2775
+ return bytes;
2254
2776
  }
2255
2777
  }
2256
2778
  if (!type) {
@@ -2276,11 +2798,13 @@ var TypeInterpreter = class _TypeInterpreter {
2276
2798
  context
2277
2799
  );
2278
2800
  }
2279
- if (isBuiltinType(type)) {
2280
- return this.parseBuiltinType(type, stream, context);
2801
+ const { typeName, args } = this.parseParameterizedType(type, context);
2802
+ const effectiveArgs = args.length > 0 ? args : typeArgs;
2803
+ if (isBuiltinType(typeName)) {
2804
+ return this.parseBuiltinType(typeName, stream, context);
2281
2805
  }
2282
- if (this.schema.types && type in this.schema.types) {
2283
- const typeSchema = this.schema.types[type];
2806
+ if (this.schema.types && typeName in this.schema.types) {
2807
+ const typeSchema = this.schema.types[typeName];
2284
2808
  const meta = this.schema.meta || this.parentMeta;
2285
2809
  if (this.schema.enums && !typeSchema.enums) {
2286
2810
  typeSchema.enums = this.schema.enums;
@@ -2289,9 +2813,100 @@ var TypeInterpreter = class _TypeInterpreter {
2289
2813
  typeSchema.types = this.schema.types;
2290
2814
  }
2291
2815
  const interpreter = new _TypeInterpreter(typeSchema, meta);
2292
- return interpreter.parse(stream, context.current, typeArgs);
2816
+ return interpreter.parse(
2817
+ stream,
2818
+ context.current,
2819
+ effectiveArgs,
2820
+ context.root
2821
+ );
2822
+ }
2823
+ throw new ParseError(`Unknown type: ${typeName}`);
2824
+ }
2825
+ /**
2826
+ * Parse parameterized type syntax and extract type name and arguments.
2827
+ * Supports: type_name(arg1, arg2, ...) or just type_name
2828
+ *
2829
+ * @param typeSpec - Type specification string
2830
+ * @param context - Execution context for evaluating argument expressions
2831
+ * @returns Object with typeName and evaluated args
2832
+ * @private
2833
+ */
2834
+ parseParameterizedType(typeSpec, context) {
2835
+ const match = typeSpec.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
2836
+ if (!match) {
2837
+ return { typeName: typeSpec, args: [] };
2838
+ }
2839
+ const typeName = match[1];
2840
+ const argsString = match[2].trim();
2841
+ if (!argsString) {
2842
+ return { typeName, args: [] };
2843
+ }
2844
+ const args = [];
2845
+ let current = "";
2846
+ let inString = false;
2847
+ let stringChar = "";
2848
+ let parenDepth = 0;
2849
+ for (let i = 0; i < argsString.length; i++) {
2850
+ const char = argsString[i];
2851
+ if (inString) {
2852
+ current += char;
2853
+ if (char === stringChar && argsString[i - 1] !== "\\") {
2854
+ inString = false;
2855
+ }
2856
+ } else if (char === '"' || char === "'") {
2857
+ inString = true;
2858
+ stringChar = char;
2859
+ current += char;
2860
+ } else if (char === "(") {
2861
+ parenDepth++;
2862
+ current += char;
2863
+ } else if (char === ")") {
2864
+ parenDepth--;
2865
+ current += char;
2866
+ } else if (char === "," && parenDepth === 0) {
2867
+ args.push(this.parseArgument(current.trim(), context));
2868
+ current = "";
2869
+ } else {
2870
+ current += char;
2871
+ }
2872
+ }
2873
+ if (current.trim()) {
2874
+ args.push(this.parseArgument(current.trim(), context));
2875
+ }
2876
+ return { typeName, args };
2877
+ }
2878
+ /**
2879
+ * Parse and evaluate a single type argument.
2880
+ *
2881
+ * @param arg - Argument string
2882
+ * @param context - Execution context
2883
+ * @returns Evaluated argument value
2884
+ * @private
2885
+ */
2886
+ parseArgument(arg, context) {
2887
+ if (arg === "true") return true;
2888
+ if (arg === "false") return false;
2889
+ if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
2890
+ return arg.slice(1, -1);
2891
+ }
2892
+ if (/^-?\d+$/.test(arg)) {
2893
+ return parseInt(arg, 10);
2894
+ }
2895
+ if (/^-?\d+\.\d+$/.test(arg)) {
2896
+ return parseFloat(arg);
2897
+ }
2898
+ if (/^0x[0-9a-f]+$/i.test(arg)) {
2899
+ return parseInt(arg, 16);
2900
+ }
2901
+ try {
2902
+ const result = this.evaluateValue(arg, context);
2903
+ if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
2904
+ return result;
2905
+ }
2906
+ return Number(result);
2907
+ } catch {
2908
+ return arg;
2293
2909
  }
2294
- throw new ParseError(`Unknown type: ${type}`);
2295
2910
  }
2296
2911
  /**
2297
2912
  * Parse a switch type (type selection based on expression).
@@ -2334,18 +2949,33 @@ var TypeInterpreter = class _TypeInterpreter {
2334
2949
  * @returns Parsed value
2335
2950
  * @private
2336
2951
  */
2337
- parseBuiltinType(type, stream, _context) {
2952
+ parseBuiltinType(type, stream, context) {
2338
2953
  const base = getBaseType(type);
2339
2954
  const typeEndian = getTypeEndianness(type);
2340
2955
  const meta = this.schema.meta || this.parentMeta;
2341
2956
  const metaEndian = meta?.endian;
2342
- const endian = typeEndian || (typeof metaEndian === "string" ? metaEndian : "le");
2957
+ let endian;
2958
+ if (typeEndian) {
2959
+ endian = typeEndian;
2960
+ } else if (typeof metaEndian === "string") {
2961
+ endian = metaEndian;
2962
+ } else if (metaEndian && typeof metaEndian === "object") {
2963
+ endian = this.evaluateEndianExpression(metaEndian, context);
2964
+ } else {
2965
+ endian = "le";
2966
+ }
2343
2967
  if (isIntegerType(type)) {
2344
2968
  return this.readInteger(base, endian, stream);
2345
2969
  }
2346
2970
  if (isFloatType(type)) {
2347
2971
  return this.readFloat(base, endian, stream);
2348
2972
  }
2973
+ if (/^b\d+$/.test(type)) {
2974
+ const n = parseInt(type.slice(1), 10);
2975
+ const val = stream.readBitsIntBe(n);
2976
+ const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
2977
+ return val <= maxSafe ? Number(val) : val;
2978
+ }
2349
2979
  if (isStringType(type)) {
2350
2980
  const encoding = this.schema.meta?.encoding || "UTF-8";
2351
2981
  if (type === "strz") {
@@ -2410,7 +3040,7 @@ var TypeInterpreter = class _TypeInterpreter {
2410
3040
  }
2411
3041
  /**
2412
3042
  * Apply processing transformation to data.
2413
- * Supports basic transformations like zlib decompression.
3043
+ * Delegates to the process utility module.
2414
3044
  *
2415
3045
  * @param data - Data to process
2416
3046
  * @param process - Processing specification
@@ -2418,13 +3048,35 @@ var TypeInterpreter = class _TypeInterpreter {
2418
3048
  * @private
2419
3049
  */
2420
3050
  applyProcessing(data, process) {
2421
- const processType = typeof process === "string" ? process : process.algorithm;
2422
- if (processType) {
2423
- throw new NotImplementedError(
2424
- `Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
2425
- );
3051
+ return applyProcess(data, process);
3052
+ }
3053
+ /**
3054
+ * Evaluate expression-based endianness (switch-on).
3055
+ *
3056
+ * @param endianExpr - Endianness expression with switch-on and cases
3057
+ * @param context - Execution context
3058
+ * @returns Resolved endianness ('le' or 'be')
3059
+ * @private
3060
+ */
3061
+ evaluateEndianExpression(endianExpr, context) {
3062
+ const switchOn = endianExpr["switch-on"];
3063
+ const cases = endianExpr.cases;
3064
+ if (!switchOn || typeof switchOn !== "string") {
3065
+ throw new ParseError('Endian expression missing "switch-on" field');
3066
+ }
3067
+ if (!cases || typeof cases !== "object") {
3068
+ throw new ParseError('Endian expression missing "cases" field');
3069
+ }
3070
+ const switchValue = this.evaluateValue(switchOn, context);
3071
+ const key = String(switchValue);
3072
+ if (key in cases) {
3073
+ const endian = cases[key];
3074
+ if (endian !== "le" && endian !== "be") {
3075
+ throw new ParseError(`Invalid endianness value: ${endian}`);
3076
+ }
3077
+ return endian;
2426
3078
  }
2427
- return data;
3079
+ return "le";
2428
3080
  }
2429
3081
  /**
2430
3082
  * Evaluate a value that can be an expression or literal.
@@ -2567,6 +3219,12 @@ function parse(ksyYaml, buffer, options = {}) {
2567
3219
  * @author Fabiano Pinto
2568
3220
  * @license MIT
2569
3221
  */
3222
+ /**
3223
+ * @fileoverview Data processing utilities for Kaitai Struct
3224
+ * @module utils/process
3225
+ * @author Fabiano Pinto
3226
+ * @license MIT
3227
+ */
2570
3228
  /**
2571
3229
  * @fileoverview Type interpreter for executing Kaitai Struct schemas
2572
3230
  * @module interpreter/TypeInterpreter