@k67/kaitai-struct-ts 0.7.3 → 0.9.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/README.md +19 -6
- package/dist/cli.js +1076 -887
- package/dist/index.d.mts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +183 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -21
package/dist/index.mjs
CHANGED
@@ -549,7 +549,9 @@ var BUILTIN_TYPES = [
|
|
549
549
|
"strz"
|
550
550
|
];
|
551
551
|
function isBuiltinType(type) {
|
552
|
-
|
552
|
+
if (BUILTIN_TYPES.includes(type)) return true;
|
553
|
+
if (/^b\d+$/.test(type)) return true;
|
554
|
+
return false;
|
553
555
|
}
|
554
556
|
function getTypeEndianness(type) {
|
555
557
|
if (type.endsWith("le")) return "le";
|
@@ -809,11 +811,83 @@ var KsyParser = class {
|
|
809
811
|
* @param imports - Map of import names to their YAML content
|
810
812
|
* @param options - Parsing options
|
811
813
|
* @returns Parsed schema with resolved imports
|
812
|
-
|
813
|
-
|
814
|
+
* @throws {ParseError} If import resolution fails
|
815
|
+
* @example
|
816
|
+
* ```typescript
|
817
|
+
* const parser = new KsyParser()
|
818
|
+
* const imports = new Map([
|
819
|
+
* ['/common/riff', riffYamlContent]
|
820
|
+
* ])
|
821
|
+
* const schema = parser.parseWithImports(wavYaml, imports)
|
822
|
+
* ```
|
823
|
+
*/
|
824
|
+
parseWithImports(mainYaml, imports, options = {}) {
|
814
825
|
const mainSchema = this.parse(mainYaml, options);
|
826
|
+
if (!mainSchema.meta.imports || mainSchema.meta.imports.length === 0) {
|
827
|
+
return mainSchema;
|
828
|
+
}
|
829
|
+
const resolvedTypes = {};
|
830
|
+
for (const importPath of mainSchema.meta.imports) {
|
831
|
+
if (!imports.has(importPath)) {
|
832
|
+
throw new ParseError(
|
833
|
+
`Import not found: ${importPath}. Available imports: ${Array.from(imports.keys()).join(", ")}`
|
834
|
+
);
|
835
|
+
}
|
836
|
+
const importYaml = imports.get(importPath);
|
837
|
+
const importedSchema = this.parse(importYaml, {
|
838
|
+
...options,
|
839
|
+
validate: false
|
840
|
+
// Skip validation for imported schemas
|
841
|
+
});
|
842
|
+
const namespace = this.extractNamespace(importPath);
|
843
|
+
if (importedSchema.types) {
|
844
|
+
for (const [typeName, typeSchema] of Object.entries(
|
845
|
+
importedSchema.types
|
846
|
+
)) {
|
847
|
+
const qualifiedName = `${namespace}::${typeName}`;
|
848
|
+
resolvedTypes[qualifiedName] = typeSchema;
|
849
|
+
}
|
850
|
+
}
|
851
|
+
resolvedTypes[namespace] = {
|
852
|
+
meta: importedSchema.meta,
|
853
|
+
seq: importedSchema.seq,
|
854
|
+
instances: importedSchema.instances,
|
855
|
+
types: importedSchema.types,
|
856
|
+
enums: importedSchema.enums
|
857
|
+
};
|
858
|
+
if (importedSchema.enums) {
|
859
|
+
if (!mainSchema.enums) {
|
860
|
+
mainSchema.enums = {};
|
861
|
+
}
|
862
|
+
for (const [enumName, enumSpec] of Object.entries(
|
863
|
+
importedSchema.enums
|
864
|
+
)) {
|
865
|
+
const qualifiedEnumName = `${namespace}::${enumName}`;
|
866
|
+
mainSchema.enums[qualifiedEnumName] = enumSpec;
|
867
|
+
}
|
868
|
+
}
|
869
|
+
}
|
870
|
+
if (Object.keys(resolvedTypes).length > 0) {
|
871
|
+
mainSchema.types = {
|
872
|
+
...resolvedTypes,
|
873
|
+
...mainSchema.types
|
874
|
+
};
|
875
|
+
}
|
815
876
|
return mainSchema;
|
816
877
|
}
|
878
|
+
/**
|
879
|
+
* Extract namespace from import path.
|
880
|
+
* Converts paths like '/common/riff' or 'common/riff' to 'riff'.
|
881
|
+
*
|
882
|
+
* @param importPath - Import path from meta.imports
|
883
|
+
* @returns Namespace identifier
|
884
|
+
* @private
|
885
|
+
*/
|
886
|
+
extractNamespace(importPath) {
|
887
|
+
const normalized = importPath.startsWith("/") ? importPath.slice(1) : importPath;
|
888
|
+
const segments = normalized.split("/");
|
889
|
+
return segments[segments.length - 1];
|
890
|
+
}
|
817
891
|
};
|
818
892
|
|
819
893
|
// src/interpreter/Context.ts
|
@@ -1124,6 +1198,17 @@ var Lexer = class {
|
|
1124
1198
|
}
|
1125
1199
|
return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
|
1126
1200
|
}
|
1201
|
+
if (this.current === "0" && this.peek() === "b") {
|
1202
|
+
value += this.current;
|
1203
|
+
this.advance();
|
1204
|
+
value += this.current;
|
1205
|
+
this.advance();
|
1206
|
+
while (this.current !== null && /[01]/.test(this.current)) {
|
1207
|
+
value += this.current;
|
1208
|
+
this.advance();
|
1209
|
+
}
|
1210
|
+
return createToken("NUMBER" /* NUMBER */, parseInt(value, 2), start);
|
1211
|
+
}
|
1127
1212
|
while (this.current !== null && this.isDigit(this.current)) {
|
1128
1213
|
value += this.current;
|
1129
1214
|
this.advance();
|
@@ -1335,6 +1420,9 @@ function createMethodCall(object, method, args) {
|
|
1335
1420
|
function createEnumAccess(enumName, value) {
|
1336
1421
|
return { kind: "EnumAccess", enumName, value };
|
1337
1422
|
}
|
1423
|
+
function createArrayLiteral(elements) {
|
1424
|
+
return { kind: "ArrayLiteral", elements };
|
1425
|
+
}
|
1338
1426
|
|
1339
1427
|
// src/expression/Parser.ts
|
1340
1428
|
var ExpressionParser = class {
|
@@ -1631,6 +1719,17 @@ var ExpressionParser = class {
|
|
1631
1719
|
this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
|
1632
1720
|
return expr;
|
1633
1721
|
}
|
1722
|
+
if (this.match("LBRACKET" /* LBRACKET */)) {
|
1723
|
+
const elements = [];
|
1724
|
+
if (this.current().type !== "RBRACKET" /* RBRACKET */) {
|
1725
|
+
elements.push(this.parseTernary());
|
1726
|
+
while (this.match("COMMA" /* COMMA */)) {
|
1727
|
+
elements.push(this.parseTernary());
|
1728
|
+
}
|
1729
|
+
}
|
1730
|
+
this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array literal");
|
1731
|
+
return createArrayLiteral(elements);
|
1732
|
+
}
|
1634
1733
|
throw new ParseError(
|
1635
1734
|
`Unexpected token: ${this.current().type}`,
|
1636
1735
|
this.current().position
|
@@ -1669,6 +1768,8 @@ var Evaluator = class {
|
|
1669
1768
|
return this.evaluateMethodCall(n.object, n.method, n.args, context);
|
1670
1769
|
case "EnumAccess":
|
1671
1770
|
return this.evaluateEnumAccess(n.enumName, n.value, context);
|
1771
|
+
case "ArrayLiteral":
|
1772
|
+
return this.evaluateArrayLiteral(n.elements, context);
|
1672
1773
|
default:
|
1673
1774
|
throw new ParseError(`Unknown AST node kind: ${node.kind}`);
|
1674
1775
|
}
|
@@ -1857,8 +1958,30 @@ var Evaluator = class {
|
|
1857
1958
|
if (typeof left === "bigint" || typeof right === "bigint") {
|
1858
1959
|
return BigInt(left) === BigInt(right);
|
1859
1960
|
}
|
1961
|
+
const toArray = (v) => {
|
1962
|
+
if (Array.isArray(v))
|
1963
|
+
return v.map((x) => typeof x === "bigint" ? Number(x) : x);
|
1964
|
+
if (v instanceof Uint8Array) return Array.from(v);
|
1965
|
+
return null;
|
1966
|
+
};
|
1967
|
+
const leftArr = toArray(left);
|
1968
|
+
const rightArr = toArray(right);
|
1969
|
+
if (leftArr && rightArr) {
|
1970
|
+
if (leftArr.length !== rightArr.length) return false;
|
1971
|
+
for (let i = 0; i < leftArr.length; i++) {
|
1972
|
+
if (leftArr[i] !== rightArr[i]) return false;
|
1973
|
+
}
|
1974
|
+
return true;
|
1975
|
+
}
|
1860
1976
|
return left === right;
|
1861
1977
|
}
|
1978
|
+
/**
|
1979
|
+
* Evaluate an array literal.
|
1980
|
+
* @private
|
1981
|
+
*/
|
1982
|
+
evaluateArrayLiteral(elements, context) {
|
1983
|
+
return elements.map((e) => this.evaluate(e, context));
|
1984
|
+
}
|
1862
1985
|
/**
|
1863
1986
|
* Helper: Convert to number.
|
1864
1987
|
* @private
|
@@ -1893,7 +2016,8 @@ var Evaluator = class {
|
|
1893
2016
|
|
1894
2017
|
// src/expression/index.ts
|
1895
2018
|
function evaluateExpression(expression, context) {
|
1896
|
-
const
|
2019
|
+
const preprocessed = expression.replace(/\.as<[^>]+>/g, "");
|
2020
|
+
const lexer = new Lexer(preprocessed);
|
1897
2021
|
const tokens = lexer.tokenize();
|
1898
2022
|
const parser = new ExpressionParser(tokens);
|
1899
2023
|
const ast = parser.parse();
|
@@ -1919,6 +2043,18 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1919
2043
|
throw new ParseError("Root schema must have meta.id");
|
1920
2044
|
}
|
1921
2045
|
}
|
2046
|
+
/**
|
2047
|
+
* Safely extract a KaitaiStream from an object that may expose `_io`.
|
2048
|
+
* Avoids using `any` casts to satisfy linting.
|
2049
|
+
*/
|
2050
|
+
static getKaitaiIO(val) {
|
2051
|
+
if (val && typeof val === "object") {
|
2052
|
+
const rec = val;
|
2053
|
+
const maybe = rec["_io"];
|
2054
|
+
if (maybe instanceof KaitaiStream) return maybe;
|
2055
|
+
}
|
2056
|
+
return null;
|
2057
|
+
}
|
1922
2058
|
/**
|
1923
2059
|
* Parse binary data according to the schema.
|
1924
2060
|
*
|
@@ -1931,6 +2067,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1931
2067
|
const result = {};
|
1932
2068
|
const context = new Context(stream, result, parent, this.schema.enums);
|
1933
2069
|
context.current = result;
|
2070
|
+
result["_io"] = stream;
|
1934
2071
|
if (typeArgs && this.schema.params) {
|
1935
2072
|
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
1936
2073
|
const param = this.schema.params[i];
|
@@ -2033,7 +2170,22 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2033
2170
|
* @private
|
2034
2171
|
*/
|
2035
2172
|
parseAttribute(attr, context) {
|
2036
|
-
|
2173
|
+
let stream = context.io;
|
2174
|
+
if (attr.io !== void 0) {
|
2175
|
+
const ioVal = this.evaluateValue(attr.io, context);
|
2176
|
+
if (ioVal instanceof KaitaiStream) {
|
2177
|
+
stream = ioVal;
|
2178
|
+
} else {
|
2179
|
+
const kio = _TypeInterpreter.getKaitaiIO(ioVal);
|
2180
|
+
if (kio) {
|
2181
|
+
stream = kio;
|
2182
|
+
} else {
|
2183
|
+
throw new ParseError(
|
2184
|
+
"io must evaluate to a KaitaiStream or an object with _io"
|
2185
|
+
);
|
2186
|
+
}
|
2187
|
+
}
|
2188
|
+
}
|
2037
2189
|
if (attr.if) {
|
2038
2190
|
const condition = this.evaluateValue(attr.if, context);
|
2039
2191
|
if (!condition) {
|
@@ -2050,9 +2202,6 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2050
2202
|
throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
|
2051
2203
|
}
|
2052
2204
|
}
|
2053
|
-
if (attr.io) {
|
2054
|
-
throw new NotImplementedError("Custom I/O streams");
|
2055
|
-
}
|
2056
2205
|
if (attr.repeat) {
|
2057
2206
|
return this.parseRepeated(attr, context);
|
2058
2207
|
}
|
@@ -2149,7 +2298,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2149
2298
|
}
|
2150
2299
|
return bytes;
|
2151
2300
|
} else {
|
2152
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2301
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2153
2302
|
const str = stream.readStr(expected.length, encoding);
|
2154
2303
|
if (str !== expected) {
|
2155
2304
|
throw new ValidationError(
|
@@ -2178,7 +2327,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2178
2327
|
throw new ParseError(`size must be non-negative, got ${size}`);
|
2179
2328
|
}
|
2180
2329
|
if (type === "str" || !type) {
|
2181
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2330
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2182
2331
|
let data;
|
2183
2332
|
if (type === "str") {
|
2184
2333
|
data = stream.readBytes(size);
|
@@ -2204,11 +2353,19 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2204
2353
|
}
|
2205
2354
|
if (attr["size-eos"]) {
|
2206
2355
|
if (type === "str") {
|
2207
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2356
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2208
2357
|
const bytes = stream.readBytesFull();
|
2209
2358
|
return new TextDecoder(encoding).decode(bytes);
|
2210
2359
|
} else {
|
2211
|
-
|
2360
|
+
let bytes = stream.readBytesFull();
|
2361
|
+
if (attr.process) {
|
2362
|
+
bytes = this.applyProcessing(bytes, attr.process);
|
2363
|
+
}
|
2364
|
+
if (type) {
|
2365
|
+
const sub = new KaitaiStream(bytes);
|
2366
|
+
return this.parseType(type, sub, context, attr["type-args"]);
|
2367
|
+
}
|
2368
|
+
return bytes;
|
2212
2369
|
}
|
2213
2370
|
}
|
2214
2371
|
if (!type) {
|
@@ -2304,8 +2461,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2304
2461
|
if (isFloatType(type)) {
|
2305
2462
|
return this.readFloat(base, endian, stream);
|
2306
2463
|
}
|
2464
|
+
if (/^b\d+$/.test(type)) {
|
2465
|
+
const n = parseInt(type.slice(1), 10);
|
2466
|
+
const val = stream.readBitsIntBe(n);
|
2467
|
+
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
|
2468
|
+
return val <= maxSafe ? Number(val) : val;
|
2469
|
+
}
|
2307
2470
|
if (isStringType(type)) {
|
2308
|
-
|
2471
|
+
const encoding = this.schema.meta?.encoding || "UTF-8";
|
2472
|
+
if (type === "strz") {
|
2473
|
+
return stream.readStrz(encoding, 0, false, true, true);
|
2474
|
+
} else if (type === "str") {
|
2475
|
+
throw new ParseError(
|
2476
|
+
"str type requires size, size-eos, or terminator attribute"
|
2477
|
+
);
|
2478
|
+
}
|
2309
2479
|
}
|
2310
2480
|
throw new ParseError(`Unknown built-in type: ${type}`);
|
2311
2481
|
}
|