@k67/kaitai-struct-ts 0.9.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/browser/index.mjs +272 -0
- package/dist/browser/index.mjs.map +1 -0
- package/dist/cli.js +568 -53
- package/dist/index.d.mts +46 -6
- package/dist/index.d.ts +46 -6
- package/dist/index.js +529 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +529 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -3
package/dist/cli.js
CHANGED
@@ -75,41 +75,72 @@ var import_yaml = require("yaml");
|
|
75
75
|
|
76
76
|
// src/utils/errors.ts
|
77
77
|
var KaitaiError = class _KaitaiError extends Error {
|
78
|
-
constructor(message, position) {
|
79
|
-
super(message);
|
78
|
+
constructor(message, position, context) {
|
79
|
+
super(_KaitaiError.formatMessage(message, position, context));
|
80
80
|
this.position = position;
|
81
|
+
this.context = context;
|
81
82
|
this.name = "KaitaiError";
|
82
83
|
Object.setPrototypeOf(this, _KaitaiError.prototype);
|
83
84
|
}
|
85
|
+
/**
|
86
|
+
* Format error message with position and context.
|
87
|
+
* @private
|
88
|
+
*/
|
89
|
+
static formatMessage(message, position, context) {
|
90
|
+
let formatted = message;
|
91
|
+
if (position !== void 0) {
|
92
|
+
formatted += ` (at byte offset 0x${position.toString(16).toUpperCase()})`;
|
93
|
+
}
|
94
|
+
if (context && context.length > 0) {
|
95
|
+
const hexContext = _KaitaiError.formatHexContext(context, position);
|
96
|
+
formatted += `
|
97
|
+
${hexContext}`;
|
98
|
+
}
|
99
|
+
return formatted;
|
100
|
+
}
|
101
|
+
/**
|
102
|
+
* Format hex dump context around error position.
|
103
|
+
* @private
|
104
|
+
*/
|
105
|
+
static formatHexContext(data, position) {
|
106
|
+
const contextSize = 16;
|
107
|
+
const start = Math.max(0, (position ?? 0) - contextSize);
|
108
|
+
const end = Math.min(data.length, (position ?? 0) + contextSize);
|
109
|
+
const chunk = data.slice(start, end);
|
110
|
+
const lines = ["Context:"];
|
111
|
+
let offset = start;
|
112
|
+
for (let i = 0; i < chunk.length; i += 16) {
|
113
|
+
const lineBytes = chunk.slice(i, i + 16);
|
114
|
+
const hex = Array.from(lineBytes).map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
115
|
+
const ascii = Array.from(lineBytes).map((b) => b >= 32 && b <= 126 ? String.fromCharCode(b) : ".").join("");
|
116
|
+
const offsetStr = ` ${(offset + i).toString(16).padStart(8, "0")}`;
|
117
|
+
const marker = position !== void 0 && position >= offset + i && position < offset + i + lineBytes.length ? " <--" : "";
|
118
|
+
lines.push(`${offsetStr}: ${hex.padEnd(48, " ")} | ${ascii}${marker}`);
|
119
|
+
}
|
120
|
+
return lines.join("\n");
|
121
|
+
}
|
84
122
|
};
|
85
123
|
var ValidationError = class _ValidationError extends KaitaiError {
|
86
|
-
constructor(message, position) {
|
87
|
-
super(message, position);
|
124
|
+
constructor(message, position, context) {
|
125
|
+
super(message, position, context);
|
88
126
|
this.name = "ValidationError";
|
89
127
|
Object.setPrototypeOf(this, _ValidationError.prototype);
|
90
128
|
}
|
91
129
|
};
|
92
130
|
var ParseError = class _ParseError extends KaitaiError {
|
93
|
-
constructor(message, position) {
|
94
|
-
super(message, position);
|
131
|
+
constructor(message, position, context) {
|
132
|
+
super(message, position, context);
|
95
133
|
this.name = "ParseError";
|
96
134
|
Object.setPrototypeOf(this, _ParseError.prototype);
|
97
135
|
}
|
98
136
|
};
|
99
137
|
var EOFError = class _EOFError extends KaitaiError {
|
100
|
-
constructor(message = "Unexpected end of stream", position) {
|
101
|
-
super(message, position);
|
138
|
+
constructor(message = "Unexpected end of stream", position, context) {
|
139
|
+
super(message, position, context);
|
102
140
|
this.name = "EOFError";
|
103
141
|
Object.setPrototypeOf(this, _EOFError.prototype);
|
104
142
|
}
|
105
143
|
};
|
106
|
-
var NotImplementedError = class _NotImplementedError extends KaitaiError {
|
107
|
-
constructor(feature) {
|
108
|
-
super(`Feature not yet implemented: ${feature}`);
|
109
|
-
this.name = "NotImplementedError";
|
110
|
-
Object.setPrototypeOf(this, _NotImplementedError.prototype);
|
111
|
-
}
|
112
|
-
};
|
113
144
|
|
114
145
|
// src/parser/KsyParser.ts
|
115
146
|
var KsyParser = class {
|
@@ -1826,15 +1857,15 @@ var Evaluator = class {
|
|
1826
1857
|
return !this.equals(leftVal, rightVal);
|
1827
1858
|
// Bitwise
|
1828
1859
|
case "<<":
|
1829
|
-
return this.
|
1860
|
+
return this.bitwiseOp(leftVal, rightVal, (a, b) => a << b);
|
1830
1861
|
case ">>":
|
1831
|
-
return this.
|
1862
|
+
return this.bitwiseOp(leftVal, rightVal, (a, b) => a >> b);
|
1832
1863
|
case "&":
|
1833
|
-
return this.
|
1864
|
+
return this.bitwiseOp(leftVal, rightVal, (a, b) => a & b);
|
1834
1865
|
case "|":
|
1835
|
-
return this.
|
1866
|
+
return this.bitwiseOp(leftVal, rightVal, (a, b) => a | b);
|
1836
1867
|
case "^":
|
1837
|
-
return this.
|
1868
|
+
return this.bitwiseOp(leftVal, rightVal, (a, b) => a ^ b);
|
1838
1869
|
// Logical
|
1839
1870
|
case "and":
|
1840
1871
|
return this.toBoolean(leftVal) && this.toBoolean(rightVal);
|
@@ -1878,6 +1909,16 @@ var Evaluator = class {
|
|
1878
1909
|
`Cannot access property ${property} of null/undefined`
|
1879
1910
|
);
|
1880
1911
|
}
|
1912
|
+
if (property === "to_i") {
|
1913
|
+
if (typeof obj === "number") return Math.floor(obj);
|
1914
|
+
if (typeof obj === "bigint") return Number(obj);
|
1915
|
+
if (typeof obj === "string") return parseInt(obj, 10);
|
1916
|
+
if (typeof obj === "boolean") return obj ? 1 : 0;
|
1917
|
+
return this.toInt(obj);
|
1918
|
+
}
|
1919
|
+
if (property === "to_s") {
|
1920
|
+
return String(obj);
|
1921
|
+
}
|
1881
1922
|
if (typeof obj === "object") {
|
1882
1923
|
return obj[property];
|
1883
1924
|
}
|
@@ -1904,8 +1945,9 @@ var Evaluator = class {
|
|
1904
1945
|
* Evaluate method call (object.method()).
|
1905
1946
|
* @private
|
1906
1947
|
*/
|
1907
|
-
evaluateMethodCall(object, method,
|
1948
|
+
evaluateMethodCall(object, method, args, context) {
|
1908
1949
|
const obj = this.evaluate(object, context);
|
1950
|
+
const evalArgs = args.map((arg) => this.evaluate(arg, context));
|
1909
1951
|
if (method === "length" || method === "size") {
|
1910
1952
|
if (Array.isArray(obj)) return obj.length;
|
1911
1953
|
if (obj instanceof Uint8Array) return obj.length;
|
@@ -1913,13 +1955,182 @@ var Evaluator = class {
|
|
1913
1955
|
throw new ParseError(`Object does not have a ${method} property`);
|
1914
1956
|
}
|
1915
1957
|
if (method === "to_i") {
|
1958
|
+
const base = evalArgs.length > 0 ? this.toInt(evalArgs[0]) : 10;
|
1959
|
+
if (typeof obj === "string") {
|
1960
|
+
return parseInt(obj, base);
|
1961
|
+
}
|
1916
1962
|
return this.toInt(obj);
|
1917
1963
|
}
|
1918
1964
|
if (method === "to_s") {
|
1919
1965
|
return String(obj);
|
1920
1966
|
}
|
1967
|
+
if (typeof obj === "string") {
|
1968
|
+
return this.evaluateStringMethod(obj, method, evalArgs);
|
1969
|
+
}
|
1970
|
+
if (Array.isArray(obj) || obj instanceof Uint8Array) {
|
1971
|
+
return this.evaluateArrayMethod(obj, method, evalArgs);
|
1972
|
+
}
|
1921
1973
|
throw new ParseError(`Unknown method: ${method}`);
|
1922
1974
|
}
|
1975
|
+
/**
|
1976
|
+
* Evaluate string methods.
|
1977
|
+
* @private
|
1978
|
+
*/
|
1979
|
+
evaluateStringMethod(str, method, args) {
|
1980
|
+
switch (method) {
|
1981
|
+
case "substring": {
|
1982
|
+
const start = args.length > 0 ? this.toInt(args[0]) : 0;
|
1983
|
+
const end = args.length > 1 ? this.toInt(args[1]) : void 0;
|
1984
|
+
return str.substring(start, end);
|
1985
|
+
}
|
1986
|
+
case "substr": {
|
1987
|
+
const start = args.length > 0 ? this.toInt(args[0]) : 0;
|
1988
|
+
const length = args.length > 1 ? this.toInt(args[1]) : void 0;
|
1989
|
+
return str.substr(start, length);
|
1990
|
+
}
|
1991
|
+
case "reverse":
|
1992
|
+
return str.split("").reverse().join("");
|
1993
|
+
case "to_i": {
|
1994
|
+
const base = args.length > 0 ? this.toInt(args[0]) : 10;
|
1995
|
+
return parseInt(str, base);
|
1996
|
+
}
|
1997
|
+
case "length":
|
1998
|
+
case "size":
|
1999
|
+
return str.length;
|
2000
|
+
// Ruby-style string methods used in Kaitai
|
2001
|
+
case "upcase":
|
2002
|
+
case "to_upper":
|
2003
|
+
return str.toUpperCase();
|
2004
|
+
case "downcase":
|
2005
|
+
case "to_lower":
|
2006
|
+
return str.toLowerCase();
|
2007
|
+
case "capitalize":
|
2008
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
2009
|
+
case "strip":
|
2010
|
+
case "trim":
|
2011
|
+
return str.trim();
|
2012
|
+
case "lstrip":
|
2013
|
+
case "trim_start":
|
2014
|
+
return str.trimStart();
|
2015
|
+
case "rstrip":
|
2016
|
+
case "trim_end":
|
2017
|
+
return str.trimEnd();
|
2018
|
+
case "starts_with":
|
2019
|
+
case "startsWith": {
|
2020
|
+
if (args.length === 0) {
|
2021
|
+
throw new ParseError("starts_with requires 1 argument");
|
2022
|
+
}
|
2023
|
+
return str.startsWith(String(args[0]));
|
2024
|
+
}
|
2025
|
+
case "ends_with":
|
2026
|
+
case "endsWith": {
|
2027
|
+
if (args.length === 0) {
|
2028
|
+
throw new ParseError("ends_with requires 1 argument");
|
2029
|
+
}
|
2030
|
+
return str.endsWith(String(args[0]));
|
2031
|
+
}
|
2032
|
+
case "includes":
|
2033
|
+
case "contains": {
|
2034
|
+
if (args.length === 0) {
|
2035
|
+
throw new ParseError("includes requires 1 argument");
|
2036
|
+
}
|
2037
|
+
return str.includes(String(args[0]));
|
2038
|
+
}
|
2039
|
+
case "index_of":
|
2040
|
+
case "indexOf": {
|
2041
|
+
if (args.length === 0) {
|
2042
|
+
throw new ParseError("index_of requires 1 argument");
|
2043
|
+
}
|
2044
|
+
return str.indexOf(String(args[0]));
|
2045
|
+
}
|
2046
|
+
case "split": {
|
2047
|
+
if (args.length === 0) {
|
2048
|
+
throw new ParseError("split requires 1 argument");
|
2049
|
+
}
|
2050
|
+
return str.split(String(args[0]));
|
2051
|
+
}
|
2052
|
+
case "replace": {
|
2053
|
+
if (args.length < 2) {
|
2054
|
+
throw new ParseError("replace requires 2 arguments");
|
2055
|
+
}
|
2056
|
+
return str.replace(String(args[0]), String(args[1]));
|
2057
|
+
}
|
2058
|
+
case "replace_all":
|
2059
|
+
case "replaceAll": {
|
2060
|
+
if (args.length < 2) {
|
2061
|
+
throw new ParseError("replace_all requires 2 arguments");
|
2062
|
+
}
|
2063
|
+
const search = String(args[0]);
|
2064
|
+
const replace = String(args[1]);
|
2065
|
+
return str.split(search).join(replace);
|
2066
|
+
}
|
2067
|
+
case "pad_left":
|
2068
|
+
case "padStart": {
|
2069
|
+
if (args.length === 0) {
|
2070
|
+
throw new ParseError("pad_left requires at least 1 argument");
|
2071
|
+
}
|
2072
|
+
const length = this.toInt(args[0]);
|
2073
|
+
const fillString = args.length > 1 ? String(args[1]) : " ";
|
2074
|
+
return str.padStart(length, fillString);
|
2075
|
+
}
|
2076
|
+
case "pad_right":
|
2077
|
+
case "padEnd": {
|
2078
|
+
if (args.length === 0) {
|
2079
|
+
throw new ParseError("pad_right requires at least 1 argument");
|
2080
|
+
}
|
2081
|
+
const length = this.toInt(args[0]);
|
2082
|
+
const fillString = args.length > 1 ? String(args[1]) : " ";
|
2083
|
+
return str.padEnd(length, fillString);
|
2084
|
+
}
|
2085
|
+
default:
|
2086
|
+
throw new ParseError(`Unknown string method: ${method}`);
|
2087
|
+
}
|
2088
|
+
}
|
2089
|
+
/**
|
2090
|
+
* Evaluate array methods.
|
2091
|
+
* @private
|
2092
|
+
*/
|
2093
|
+
evaluateArrayMethod(arr, method, args) {
|
2094
|
+
const array = Array.isArray(arr) ? arr : Array.from(arr);
|
2095
|
+
switch (method) {
|
2096
|
+
case "length":
|
2097
|
+
case "size":
|
2098
|
+
return array.length;
|
2099
|
+
case "first":
|
2100
|
+
return array[0];
|
2101
|
+
case "last":
|
2102
|
+
return array[array.length - 1];
|
2103
|
+
case "min":
|
2104
|
+
return Math.min(...array.map((v) => this.toNumber(v)));
|
2105
|
+
case "max":
|
2106
|
+
return Math.max(...array.map((v) => this.toNumber(v)));
|
2107
|
+
case "reverse":
|
2108
|
+
return [...array].reverse();
|
2109
|
+
case "sort":
|
2110
|
+
return [...array].sort((a, b) => this.compare(a, b));
|
2111
|
+
case "includes":
|
2112
|
+
case "contains": {
|
2113
|
+
if (args.length === 0) {
|
2114
|
+
throw new ParseError("includes requires 1 argument");
|
2115
|
+
}
|
2116
|
+
return array.some((item) => this.equals(item, args[0]));
|
2117
|
+
}
|
2118
|
+
case "index_of":
|
2119
|
+
case "indexOf": {
|
2120
|
+
if (args.length === 0) {
|
2121
|
+
throw new ParseError("index_of requires 1 argument");
|
2122
|
+
}
|
2123
|
+
return array.findIndex((item) => this.equals(item, args[0]));
|
2124
|
+
}
|
2125
|
+
case "slice": {
|
2126
|
+
const start = args.length > 0 ? this.toInt(args[0]) : 0;
|
2127
|
+
const end = args.length > 1 ? this.toInt(args[1]) : void 0;
|
2128
|
+
return array.slice(start, end);
|
2129
|
+
}
|
2130
|
+
default:
|
2131
|
+
throw new ParseError(`Unknown array method: ${method}`);
|
2132
|
+
}
|
2133
|
+
}
|
1923
2134
|
/**
|
1924
2135
|
* Evaluate enum access (EnumName::value).
|
1925
2136
|
* @private
|
@@ -1949,6 +2160,38 @@ var Evaluator = class {
|
|
1949
2160
|
const result = a % b;
|
1950
2161
|
return result < 0 ? result + b : result;
|
1951
2162
|
}
|
2163
|
+
/**
|
2164
|
+
* Helper: Bitwise operation with BigInt support.
|
2165
|
+
* JavaScript bitwise operators work on 32-bit integers, but Kaitai
|
2166
|
+
* may use 64-bit values. For values that fit in 32 bits, use native ops.
|
2167
|
+
* For larger values, convert to BigInt (with limitations).
|
2168
|
+
* @private
|
2169
|
+
*/
|
2170
|
+
bitwiseOp(left, right, op) {
|
2171
|
+
if (typeof left === "bigint" || typeof right === "bigint") {
|
2172
|
+
const leftBig = typeof left === "bigint" ? left : BigInt(left);
|
2173
|
+
const rightBig = typeof right === "bigint" ? right : BigInt(right);
|
2174
|
+
if (op.toString().includes("<<")) {
|
2175
|
+
return leftBig << BigInt(Number(rightBig));
|
2176
|
+
}
|
2177
|
+
if (op.toString().includes(">>")) {
|
2178
|
+
return leftBig >> BigInt(Number(rightBig));
|
2179
|
+
}
|
2180
|
+
if (op.toString().includes("&")) {
|
2181
|
+
return leftBig & rightBig;
|
2182
|
+
}
|
2183
|
+
if (op.toString().includes("|")) {
|
2184
|
+
return leftBig | rightBig;
|
2185
|
+
}
|
2186
|
+
if (op.toString().includes("^")) {
|
2187
|
+
return leftBig ^ rightBig;
|
2188
|
+
}
|
2189
|
+
}
|
2190
|
+
if (left === void 0 || left === null || right === void 0 || right === null) {
|
2191
|
+
throw new ParseError("Cannot perform bitwise operation on null/undefined");
|
2192
|
+
}
|
2193
|
+
return op(this.toInt(left), this.toInt(right));
|
2194
|
+
}
|
1952
2195
|
/**
|
1953
2196
|
* Helper: Compare two values.
|
1954
2197
|
* @private
|
@@ -2036,6 +2279,112 @@ function evaluateExpression(expression, context) {
|
|
2036
2279
|
return evaluator.evaluate(ast, context);
|
2037
2280
|
}
|
2038
2281
|
|
2282
|
+
// src/utils/process.ts
|
2283
|
+
var import_pako = require("pako");
|
2284
|
+
function applyProcess(data, process2) {
|
2285
|
+
const spec = typeof process2 === "string" ? { algorithm: process2 } : process2;
|
2286
|
+
const algorithm = spec.algorithm;
|
2287
|
+
if (!algorithm) {
|
2288
|
+
throw new ParseError("Process specification missing algorithm");
|
2289
|
+
}
|
2290
|
+
switch (algorithm) {
|
2291
|
+
case "zlib":
|
2292
|
+
return processZlib(data);
|
2293
|
+
case "xor":
|
2294
|
+
return processXor(data, spec.key);
|
2295
|
+
case "rol":
|
2296
|
+
return processRol(data, spec.amount, spec.group);
|
2297
|
+
case "ror":
|
2298
|
+
return processRor(data, spec.amount, spec.group);
|
2299
|
+
case "bswap2":
|
2300
|
+
return processByteswap(data, 2);
|
2301
|
+
case "bswap4":
|
2302
|
+
return processByteswap(data, 4);
|
2303
|
+
case "bswap8":
|
2304
|
+
return processByteswap(data, 8);
|
2305
|
+
case "bswap16":
|
2306
|
+
return processByteswap(data, 16);
|
2307
|
+
default:
|
2308
|
+
throw new ParseError(
|
2309
|
+
`Unknown process algorithm: ${algorithm}. Supported: zlib, xor, rol, ror, bswap2, bswap4, bswap8, bswap16`
|
2310
|
+
);
|
2311
|
+
}
|
2312
|
+
}
|
2313
|
+
function processZlib(data) {
|
2314
|
+
try {
|
2315
|
+
return (0, import_pako.inflate)(data);
|
2316
|
+
} catch (error) {
|
2317
|
+
throw new ParseError(
|
2318
|
+
`Zlib decompression failed: ${error instanceof Error ? error.message : String(error)}`
|
2319
|
+
);
|
2320
|
+
}
|
2321
|
+
}
|
2322
|
+
function processXor(data, key) {
|
2323
|
+
if (key === void 0) {
|
2324
|
+
throw new ParseError("XOR process requires a key parameter");
|
2325
|
+
}
|
2326
|
+
const result = new Uint8Array(data.length);
|
2327
|
+
const keyBytes = Array.isArray(key) ? key : [key];
|
2328
|
+
if (keyBytes.length === 0) {
|
2329
|
+
throw new ParseError("XOR key cannot be empty");
|
2330
|
+
}
|
2331
|
+
for (let i = 0; i < data.length; i++) {
|
2332
|
+
result[i] = data[i] ^ keyBytes[i % keyBytes.length];
|
2333
|
+
}
|
2334
|
+
return result;
|
2335
|
+
}
|
2336
|
+
function processRol(data, amount, group) {
|
2337
|
+
const bits = amount ?? 1;
|
2338
|
+
const groupSize = group ?? 1;
|
2339
|
+
if (bits < 0 || bits > 7) {
|
2340
|
+
throw new ParseError("ROL amount must be between 0 and 7");
|
2341
|
+
}
|
2342
|
+
if (groupSize !== 1) {
|
2343
|
+
throw new ParseError("ROL with group size > 1 not yet supported");
|
2344
|
+
}
|
2345
|
+
const result = new Uint8Array(data.length);
|
2346
|
+
for (let i = 0; i < data.length; i++) {
|
2347
|
+
const byte = data[i];
|
2348
|
+
result[i] = (byte << bits | byte >> 8 - bits) & 255;
|
2349
|
+
}
|
2350
|
+
return result;
|
2351
|
+
}
|
2352
|
+
function processRor(data, amount, group) {
|
2353
|
+
const bits = amount ?? 1;
|
2354
|
+
const groupSize = group ?? 1;
|
2355
|
+
if (bits < 0 || bits > 7) {
|
2356
|
+
throw new ParseError("ROR amount must be between 0 and 7");
|
2357
|
+
}
|
2358
|
+
if (groupSize !== 1) {
|
2359
|
+
throw new ParseError("ROR with group size > 1 not yet supported");
|
2360
|
+
}
|
2361
|
+
const result = new Uint8Array(data.length);
|
2362
|
+
for (let i = 0; i < data.length; i++) {
|
2363
|
+
const byte = data[i];
|
2364
|
+
result[i] = (byte >> bits | byte << 8 - bits) & 255;
|
2365
|
+
}
|
2366
|
+
return result;
|
2367
|
+
}
|
2368
|
+
function processByteswap(data, groupSize) {
|
2369
|
+
if (![2, 4, 8, 16].includes(groupSize)) {
|
2370
|
+
throw new ParseError(
|
2371
|
+
`Invalid byteswap group size: ${groupSize}. Must be 2, 4, 8, or 16`
|
2372
|
+
);
|
2373
|
+
}
|
2374
|
+
if (data.length % groupSize !== 0) {
|
2375
|
+
throw new ParseError(
|
2376
|
+
`Data length ${data.length} is not aligned to group size ${groupSize}`
|
2377
|
+
);
|
2378
|
+
}
|
2379
|
+
const result = new Uint8Array(data.length);
|
2380
|
+
for (let i = 0; i < data.length; i += groupSize) {
|
2381
|
+
for (let j = 0; j < groupSize; j++) {
|
2382
|
+
result[i + j] = data[i + groupSize - 1 - j];
|
2383
|
+
}
|
2384
|
+
}
|
2385
|
+
return result;
|
2386
|
+
}
|
2387
|
+
|
2039
2388
|
// src/interpreter/TypeInterpreter.ts
|
2040
2389
|
var TypeInterpreter = class _TypeInterpreter {
|
2041
2390
|
/**
|
@@ -2072,13 +2421,20 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2072
2421
|
* @param stream - Binary stream to parse
|
2073
2422
|
* @param parent - Parent object (for nested types)
|
2074
2423
|
* @param typeArgs - Arguments for parametric types
|
2424
|
+
* @param root - Root object of the parse tree (for nested types)
|
2075
2425
|
* @returns Parsed object
|
2076
2426
|
*/
|
2077
|
-
parse(stream, parent, typeArgs) {
|
2427
|
+
parse(stream, parent, typeArgs, root) {
|
2078
2428
|
const result = {};
|
2079
|
-
const
|
2429
|
+
const actualRoot = root || result;
|
2430
|
+
const context = new Context(stream, actualRoot, parent, this.schema.enums);
|
2080
2431
|
context.current = result;
|
2432
|
+
const startPos = stream.pos;
|
2081
2433
|
result["_io"] = stream;
|
2434
|
+
if (root) {
|
2435
|
+
;
|
2436
|
+
result["_root"] = root;
|
2437
|
+
}
|
2082
2438
|
if (typeArgs && this.schema.params) {
|
2083
2439
|
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
2084
2440
|
const param = this.schema.params[i];
|
@@ -2087,6 +2443,9 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2087
2443
|
context.set(param.id, evaluatedArg);
|
2088
2444
|
}
|
2089
2445
|
}
|
2446
|
+
if (this.schema.instances) {
|
2447
|
+
this.setupInstances(result, stream, context);
|
2448
|
+
}
|
2090
2449
|
if (this.schema.seq) {
|
2091
2450
|
for (const attr of this.schema.seq) {
|
2092
2451
|
const value = this.parseAttribute(attr, context);
|
@@ -2095,9 +2454,8 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2095
2454
|
}
|
2096
2455
|
}
|
2097
2456
|
}
|
2098
|
-
|
2099
|
-
|
2100
|
-
}
|
2457
|
+
const endPos = stream.pos;
|
2458
|
+
result["_sizeof"] = endPos - startPos;
|
2101
2459
|
return result;
|
2102
2460
|
}
|
2103
2461
|
/**
|
@@ -2402,11 +2760,13 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2402
2760
|
context
|
2403
2761
|
);
|
2404
2762
|
}
|
2405
|
-
|
2406
|
-
|
2763
|
+
const { typeName, args } = this.parseParameterizedType(type, context);
|
2764
|
+
const effectiveArgs = args.length > 0 ? args : typeArgs;
|
2765
|
+
if (isBuiltinType(typeName)) {
|
2766
|
+
return this.parseBuiltinType(typeName, stream, context);
|
2407
2767
|
}
|
2408
|
-
if (this.schema.types &&
|
2409
|
-
const typeSchema = this.schema.types[
|
2768
|
+
if (this.schema.types && typeName in this.schema.types) {
|
2769
|
+
const typeSchema = this.schema.types[typeName];
|
2410
2770
|
const meta = this.schema.meta || this.parentMeta;
|
2411
2771
|
if (this.schema.enums && !typeSchema.enums) {
|
2412
2772
|
typeSchema.enums = this.schema.enums;
|
@@ -2415,9 +2775,100 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2415
2775
|
typeSchema.types = this.schema.types;
|
2416
2776
|
}
|
2417
2777
|
const interpreter = new _TypeInterpreter(typeSchema, meta);
|
2418
|
-
return interpreter.parse(
|
2778
|
+
return interpreter.parse(
|
2779
|
+
stream,
|
2780
|
+
context.current,
|
2781
|
+
effectiveArgs,
|
2782
|
+
context.root
|
2783
|
+
);
|
2784
|
+
}
|
2785
|
+
throw new ParseError(`Unknown type: ${typeName}`);
|
2786
|
+
}
|
2787
|
+
/**
|
2788
|
+
* Parse parameterized type syntax and extract type name and arguments.
|
2789
|
+
* Supports: type_name(arg1, arg2, ...) or just type_name
|
2790
|
+
*
|
2791
|
+
* @param typeSpec - Type specification string
|
2792
|
+
* @param context - Execution context for evaluating argument expressions
|
2793
|
+
* @returns Object with typeName and evaluated args
|
2794
|
+
* @private
|
2795
|
+
*/
|
2796
|
+
parseParameterizedType(typeSpec, context) {
|
2797
|
+
const match = typeSpec.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
|
2798
|
+
if (!match) {
|
2799
|
+
return { typeName: typeSpec, args: [] };
|
2800
|
+
}
|
2801
|
+
const typeName = match[1];
|
2802
|
+
const argsString = match[2].trim();
|
2803
|
+
if (!argsString) {
|
2804
|
+
return { typeName, args: [] };
|
2805
|
+
}
|
2806
|
+
const args = [];
|
2807
|
+
let current = "";
|
2808
|
+
let inString = false;
|
2809
|
+
let stringChar = "";
|
2810
|
+
let parenDepth = 0;
|
2811
|
+
for (let i = 0; i < argsString.length; i++) {
|
2812
|
+
const char = argsString[i];
|
2813
|
+
if (inString) {
|
2814
|
+
current += char;
|
2815
|
+
if (char === stringChar && argsString[i - 1] !== "\\") {
|
2816
|
+
inString = false;
|
2817
|
+
}
|
2818
|
+
} else if (char === '"' || char === "'") {
|
2819
|
+
inString = true;
|
2820
|
+
stringChar = char;
|
2821
|
+
current += char;
|
2822
|
+
} else if (char === "(") {
|
2823
|
+
parenDepth++;
|
2824
|
+
current += char;
|
2825
|
+
} else if (char === ")") {
|
2826
|
+
parenDepth--;
|
2827
|
+
current += char;
|
2828
|
+
} else if (char === "," && parenDepth === 0) {
|
2829
|
+
args.push(this.parseArgument(current.trim(), context));
|
2830
|
+
current = "";
|
2831
|
+
} else {
|
2832
|
+
current += char;
|
2833
|
+
}
|
2834
|
+
}
|
2835
|
+
if (current.trim()) {
|
2836
|
+
args.push(this.parseArgument(current.trim(), context));
|
2837
|
+
}
|
2838
|
+
return { typeName, args };
|
2839
|
+
}
|
2840
|
+
/**
|
2841
|
+
* Parse and evaluate a single type argument.
|
2842
|
+
*
|
2843
|
+
* @param arg - Argument string
|
2844
|
+
* @param context - Execution context
|
2845
|
+
* @returns Evaluated argument value
|
2846
|
+
* @private
|
2847
|
+
*/
|
2848
|
+
parseArgument(arg, context) {
|
2849
|
+
if (arg === "true") return true;
|
2850
|
+
if (arg === "false") return false;
|
2851
|
+
if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
|
2852
|
+
return arg.slice(1, -1);
|
2853
|
+
}
|
2854
|
+
if (/^-?\d+$/.test(arg)) {
|
2855
|
+
return parseInt(arg, 10);
|
2856
|
+
}
|
2857
|
+
if (/^-?\d+\.\d+$/.test(arg)) {
|
2858
|
+
return parseFloat(arg);
|
2859
|
+
}
|
2860
|
+
if (/^0x[0-9a-f]+$/i.test(arg)) {
|
2861
|
+
return parseInt(arg, 16);
|
2862
|
+
}
|
2863
|
+
try {
|
2864
|
+
const result = this.evaluateValue(arg, context);
|
2865
|
+
if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
|
2866
|
+
return result;
|
2867
|
+
}
|
2868
|
+
return Number(result);
|
2869
|
+
} catch {
|
2870
|
+
return arg;
|
2419
2871
|
}
|
2420
|
-
throw new ParseError(`Unknown type: ${type}`);
|
2421
2872
|
}
|
2422
2873
|
/**
|
2423
2874
|
* Parse a switch type (type selection based on expression).
|
@@ -2460,12 +2911,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2460
2911
|
* @returns Parsed value
|
2461
2912
|
* @private
|
2462
2913
|
*/
|
2463
|
-
parseBuiltinType(type, stream,
|
2914
|
+
parseBuiltinType(type, stream, context) {
|
2464
2915
|
const base = getBaseType(type);
|
2465
2916
|
const typeEndian = getTypeEndianness(type);
|
2466
2917
|
const meta = this.schema.meta || this.parentMeta;
|
2467
2918
|
const metaEndian = meta?.endian;
|
2468
|
-
|
2919
|
+
let endian;
|
2920
|
+
if (typeEndian) {
|
2921
|
+
endian = typeEndian;
|
2922
|
+
} else if (typeof metaEndian === "string") {
|
2923
|
+
endian = metaEndian;
|
2924
|
+
} else if (metaEndian && typeof metaEndian === "object") {
|
2925
|
+
endian = this.evaluateEndianExpression(metaEndian, context);
|
2926
|
+
} else {
|
2927
|
+
endian = "le";
|
2928
|
+
}
|
2469
2929
|
if (isIntegerType(type)) {
|
2470
2930
|
return this.readInteger(base, endian, stream);
|
2471
2931
|
}
|
@@ -2542,7 +3002,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2542
3002
|
}
|
2543
3003
|
/**
|
2544
3004
|
* Apply processing transformation to data.
|
2545
|
-
*
|
3005
|
+
* Delegates to the process utility module.
|
2546
3006
|
*
|
2547
3007
|
* @param data - Data to process
|
2548
3008
|
* @param process - Processing specification
|
@@ -2550,13 +3010,35 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2550
3010
|
* @private
|
2551
3011
|
*/
|
2552
3012
|
applyProcessing(data, process2) {
|
2553
|
-
|
2554
|
-
|
2555
|
-
|
2556
|
-
|
2557
|
-
|
3013
|
+
return applyProcess(data, process2);
|
3014
|
+
}
|
3015
|
+
/**
|
3016
|
+
* Evaluate expression-based endianness (switch-on).
|
3017
|
+
*
|
3018
|
+
* @param endianExpr - Endianness expression with switch-on and cases
|
3019
|
+
* @param context - Execution context
|
3020
|
+
* @returns Resolved endianness ('le' or 'be')
|
3021
|
+
* @private
|
3022
|
+
*/
|
3023
|
+
evaluateEndianExpression(endianExpr, context) {
|
3024
|
+
const switchOn = endianExpr["switch-on"];
|
3025
|
+
const cases = endianExpr.cases;
|
3026
|
+
if (!switchOn || typeof switchOn !== "string") {
|
3027
|
+
throw new ParseError('Endian expression missing "switch-on" field');
|
3028
|
+
}
|
3029
|
+
if (!cases || typeof cases !== "object") {
|
3030
|
+
throw new ParseError('Endian expression missing "cases" field');
|
2558
3031
|
}
|
2559
|
-
|
3032
|
+
const switchValue = this.evaluateValue(switchOn, context);
|
3033
|
+
const key = String(switchValue);
|
3034
|
+
if (key in cases) {
|
3035
|
+
const endian = cases[key];
|
3036
|
+
if (endian !== "le" && endian !== "be") {
|
3037
|
+
throw new ParseError(`Invalid endianness value: ${endian}`);
|
3038
|
+
}
|
3039
|
+
return endian;
|
3040
|
+
}
|
3041
|
+
return "le";
|
2560
3042
|
}
|
2561
3043
|
/**
|
2562
3044
|
* Evaluate a value that can be an expression or literal.
|
@@ -2755,17 +3237,44 @@ function extractField(obj, path) {
|
|
2755
3237
|
}
|
2756
3238
|
return current;
|
2757
3239
|
}
|
2758
|
-
function
|
2759
|
-
|
2760
|
-
|
3240
|
+
function safeStringify(data, pretty) {
|
3241
|
+
function safeClone(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
3242
|
+
if (obj === null || obj === void 0) return obj;
|
3243
|
+
if (typeof obj === "bigint") return String(obj);
|
3244
|
+
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean")
|
3245
|
+
return obj;
|
3246
|
+
if (obj instanceof Uint8Array) return Array.from(obj);
|
3247
|
+
if (typeof obj === "object") {
|
3248
|
+
if (seen.has(obj)) return "[Circular]";
|
3249
|
+
seen.add(obj);
|
3250
|
+
}
|
3251
|
+
if (Array.isArray(obj)) {
|
3252
|
+
return obj.map((item) => safeClone(item, seen));
|
3253
|
+
}
|
3254
|
+
if (typeof obj === "object") {
|
3255
|
+
const result = {};
|
3256
|
+
const objRecord = obj;
|
3257
|
+
for (const key in objRecord) {
|
3258
|
+
if (key === "_io" || key === "_root" || key === "_parent") continue;
|
3259
|
+
try {
|
3260
|
+
const value = objRecord[key];
|
3261
|
+
result[key] = safeClone(value, seen);
|
3262
|
+
} catch (error) {
|
3263
|
+
result[key] = `[Error: ${error instanceof Error ? error.message : "unavailable"}]`;
|
3264
|
+
}
|
3265
|
+
}
|
3266
|
+
return result;
|
3267
|
+
}
|
3268
|
+
return obj;
|
2761
3269
|
}
|
2762
|
-
|
3270
|
+
const safe = safeClone(data);
|
3271
|
+
return pretty ? JSON.stringify(safe, null, 2) : JSON.stringify(safe);
|
2763
3272
|
}
|
2764
3273
|
function formatOutput(data, format, pretty) {
|
2765
3274
|
if (format === "yaml") {
|
2766
|
-
return
|
3275
|
+
return safeStringify(data, true).replace(/^{$/gm, "").replace(/^}$/gm, "").replace(/^\s*"([^"]+)":\s*/gm, "$1: ").replace(/,$/gm, "");
|
2767
3276
|
}
|
2768
|
-
return
|
3277
|
+
return safeStringify(data, pretty);
|
2769
3278
|
}
|
2770
3279
|
function main() {
|
2771
3280
|
const { options, positional } = parseCliArgs();
|
@@ -2912,12 +3421,6 @@ main();
|
|
2912
3421
|
* @author Fabiano Pinto
|
2913
3422
|
* @license MIT
|
2914
3423
|
*/
|
2915
|
-
/**
|
2916
|
-
* @fileoverview Binary stream reading functionality
|
2917
|
-
* @module stream
|
2918
|
-
* @author Fabiano Pinto
|
2919
|
-
* @license MIT
|
2920
|
-
*/
|
2921
3424
|
/**
|
2922
3425
|
* @fileoverview Token types for Kaitai Struct expression language
|
2923
3426
|
* @module expression/Token
|
@@ -2954,6 +3457,12 @@ main();
|
|
2954
3457
|
* @author Fabiano Pinto
|
2955
3458
|
* @license MIT
|
2956
3459
|
*/
|
3460
|
+
/**
|
3461
|
+
* @fileoverview Data processing utilities for Kaitai Struct
|
3462
|
+
* @module utils/process
|
3463
|
+
* @author Fabiano Pinto
|
3464
|
+
* @license MIT
|
3465
|
+
*/
|
2957
3466
|
/**
|
2958
3467
|
* @fileoverview Type interpreter for executing Kaitai Struct schemas
|
2959
3468
|
* @module interpreter/TypeInterpreter
|
@@ -2966,6 +3475,12 @@ main();
|
|
2966
3475
|
* @author Fabiano Pinto
|
2967
3476
|
* @license MIT
|
2968
3477
|
*/
|
3478
|
+
/**
|
3479
|
+
* @fileoverview Binary stream reading functionality
|
3480
|
+
* @module stream
|
3481
|
+
* @author Fabiano Pinto
|
3482
|
+
* @license MIT
|
3483
|
+
*/
|
2969
3484
|
/**
|
2970
3485
|
* @fileoverview CLI utility for parsing binary files with Kaitai Struct definitions
|
2971
3486
|
* @module kaitai-struct-ts/cli
|