@k67/kaitai-struct-ts 0.8.0 → 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/dist/cli.js +1060 -884
- package/dist/index.d.mts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +175 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +175 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
@@ -560,8 +560,26 @@ declare class KsyParser {
|
|
560
560
|
* @param imports - Map of import names to their YAML content
|
561
561
|
* @param options - Parsing options
|
562
562
|
* @returns Parsed schema with resolved imports
|
563
|
+
* @throws {ParseError} If import resolution fails
|
564
|
+
* @example
|
565
|
+
* ```typescript
|
566
|
+
* const parser = new KsyParser()
|
567
|
+
* const imports = new Map([
|
568
|
+
* ['/common/riff', riffYamlContent]
|
569
|
+
* ])
|
570
|
+
* const schema = parser.parseWithImports(wavYaml, imports)
|
571
|
+
* ```
|
572
|
+
*/
|
573
|
+
parseWithImports(mainYaml: string, imports: Map<string, string>, options?: ParseOptions$1): KsySchema;
|
574
|
+
/**
|
575
|
+
* Extract namespace from import path.
|
576
|
+
* Converts paths like '/common/riff' or 'common/riff' to 'riff'.
|
577
|
+
*
|
578
|
+
* @param importPath - Import path from meta.imports
|
579
|
+
* @returns Namespace identifier
|
580
|
+
* @private
|
563
581
|
*/
|
564
|
-
|
582
|
+
private extractNamespace;
|
565
583
|
}
|
566
584
|
/**
|
567
585
|
* Options for parsing .ksy files.
|
@@ -753,6 +771,11 @@ declare class TypeInterpreter {
|
|
753
771
|
endian?: Endianness | object;
|
754
772
|
encoding?: string;
|
755
773
|
} | undefined);
|
774
|
+
/**
|
775
|
+
* Safely extract a KaitaiStream from an object that may expose `_io`.
|
776
|
+
* Avoids using `any` casts to satisfy linting.
|
777
|
+
*/
|
778
|
+
private static getKaitaiIO;
|
756
779
|
/**
|
757
780
|
* Parse binary data according to the schema.
|
758
781
|
*
|
package/dist/index.d.ts
CHANGED
@@ -560,8 +560,26 @@ declare class KsyParser {
|
|
560
560
|
* @param imports - Map of import names to their YAML content
|
561
561
|
* @param options - Parsing options
|
562
562
|
* @returns Parsed schema with resolved imports
|
563
|
+
* @throws {ParseError} If import resolution fails
|
564
|
+
* @example
|
565
|
+
* ```typescript
|
566
|
+
* const parser = new KsyParser()
|
567
|
+
* const imports = new Map([
|
568
|
+
* ['/common/riff', riffYamlContent]
|
569
|
+
* ])
|
570
|
+
* const schema = parser.parseWithImports(wavYaml, imports)
|
571
|
+
* ```
|
572
|
+
*/
|
573
|
+
parseWithImports(mainYaml: string, imports: Map<string, string>, options?: ParseOptions$1): KsySchema;
|
574
|
+
/**
|
575
|
+
* Extract namespace from import path.
|
576
|
+
* Converts paths like '/common/riff' or 'common/riff' to 'riff'.
|
577
|
+
*
|
578
|
+
* @param importPath - Import path from meta.imports
|
579
|
+
* @returns Namespace identifier
|
580
|
+
* @private
|
563
581
|
*/
|
564
|
-
|
582
|
+
private extractNamespace;
|
565
583
|
}
|
566
584
|
/**
|
567
585
|
* Options for parsing .ksy files.
|
@@ -753,6 +771,11 @@ declare class TypeInterpreter {
|
|
753
771
|
endian?: Endianness | object;
|
754
772
|
encoding?: string;
|
755
773
|
} | undefined);
|
774
|
+
/**
|
775
|
+
* Safely extract a KaitaiStream from an object that may expose `_io`.
|
776
|
+
* Avoids using `any` casts to satisfy linting.
|
777
|
+
*/
|
778
|
+
private static getKaitaiIO;
|
756
779
|
/**
|
757
780
|
* Parse binary data according to the schema.
|
758
781
|
*
|
package/dist/index.js
CHANGED
@@ -591,7 +591,9 @@ var BUILTIN_TYPES = [
|
|
591
591
|
"strz"
|
592
592
|
];
|
593
593
|
function isBuiltinType(type) {
|
594
|
-
|
594
|
+
if (BUILTIN_TYPES.includes(type)) return true;
|
595
|
+
if (/^b\d+$/.test(type)) return true;
|
596
|
+
return false;
|
595
597
|
}
|
596
598
|
function getTypeEndianness(type) {
|
597
599
|
if (type.endsWith("le")) return "le";
|
@@ -851,11 +853,83 @@ var KsyParser = class {
|
|
851
853
|
* @param imports - Map of import names to their YAML content
|
852
854
|
* @param options - Parsing options
|
853
855
|
* @returns Parsed schema with resolved imports
|
854
|
-
|
855
|
-
|
856
|
+
* @throws {ParseError} If import resolution fails
|
857
|
+
* @example
|
858
|
+
* ```typescript
|
859
|
+
* const parser = new KsyParser()
|
860
|
+
* const imports = new Map([
|
861
|
+
* ['/common/riff', riffYamlContent]
|
862
|
+
* ])
|
863
|
+
* const schema = parser.parseWithImports(wavYaml, imports)
|
864
|
+
* ```
|
865
|
+
*/
|
866
|
+
parseWithImports(mainYaml, imports, options = {}) {
|
856
867
|
const mainSchema = this.parse(mainYaml, options);
|
868
|
+
if (!mainSchema.meta.imports || mainSchema.meta.imports.length === 0) {
|
869
|
+
return mainSchema;
|
870
|
+
}
|
871
|
+
const resolvedTypes = {};
|
872
|
+
for (const importPath of mainSchema.meta.imports) {
|
873
|
+
if (!imports.has(importPath)) {
|
874
|
+
throw new ParseError(
|
875
|
+
`Import not found: ${importPath}. Available imports: ${Array.from(imports.keys()).join(", ")}`
|
876
|
+
);
|
877
|
+
}
|
878
|
+
const importYaml = imports.get(importPath);
|
879
|
+
const importedSchema = this.parse(importYaml, {
|
880
|
+
...options,
|
881
|
+
validate: false
|
882
|
+
// Skip validation for imported schemas
|
883
|
+
});
|
884
|
+
const namespace = this.extractNamespace(importPath);
|
885
|
+
if (importedSchema.types) {
|
886
|
+
for (const [typeName, typeSchema] of Object.entries(
|
887
|
+
importedSchema.types
|
888
|
+
)) {
|
889
|
+
const qualifiedName = `${namespace}::${typeName}`;
|
890
|
+
resolvedTypes[qualifiedName] = typeSchema;
|
891
|
+
}
|
892
|
+
}
|
893
|
+
resolvedTypes[namespace] = {
|
894
|
+
meta: importedSchema.meta,
|
895
|
+
seq: importedSchema.seq,
|
896
|
+
instances: importedSchema.instances,
|
897
|
+
types: importedSchema.types,
|
898
|
+
enums: importedSchema.enums
|
899
|
+
};
|
900
|
+
if (importedSchema.enums) {
|
901
|
+
if (!mainSchema.enums) {
|
902
|
+
mainSchema.enums = {};
|
903
|
+
}
|
904
|
+
for (const [enumName, enumSpec] of Object.entries(
|
905
|
+
importedSchema.enums
|
906
|
+
)) {
|
907
|
+
const qualifiedEnumName = `${namespace}::${enumName}`;
|
908
|
+
mainSchema.enums[qualifiedEnumName] = enumSpec;
|
909
|
+
}
|
910
|
+
}
|
911
|
+
}
|
912
|
+
if (Object.keys(resolvedTypes).length > 0) {
|
913
|
+
mainSchema.types = {
|
914
|
+
...resolvedTypes,
|
915
|
+
...mainSchema.types
|
916
|
+
};
|
917
|
+
}
|
857
918
|
return mainSchema;
|
858
919
|
}
|
920
|
+
/**
|
921
|
+
* Extract namespace from import path.
|
922
|
+
* Converts paths like '/common/riff' or 'common/riff' to 'riff'.
|
923
|
+
*
|
924
|
+
* @param importPath - Import path from meta.imports
|
925
|
+
* @returns Namespace identifier
|
926
|
+
* @private
|
927
|
+
*/
|
928
|
+
extractNamespace(importPath) {
|
929
|
+
const normalized = importPath.startsWith("/") ? importPath.slice(1) : importPath;
|
930
|
+
const segments = normalized.split("/");
|
931
|
+
return segments[segments.length - 1];
|
932
|
+
}
|
859
933
|
};
|
860
934
|
|
861
935
|
// src/interpreter/Context.ts
|
@@ -1166,6 +1240,17 @@ var Lexer = class {
|
|
1166
1240
|
}
|
1167
1241
|
return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
|
1168
1242
|
}
|
1243
|
+
if (this.current === "0" && this.peek() === "b") {
|
1244
|
+
value += this.current;
|
1245
|
+
this.advance();
|
1246
|
+
value += this.current;
|
1247
|
+
this.advance();
|
1248
|
+
while (this.current !== null && /[01]/.test(this.current)) {
|
1249
|
+
value += this.current;
|
1250
|
+
this.advance();
|
1251
|
+
}
|
1252
|
+
return createToken("NUMBER" /* NUMBER */, parseInt(value, 2), start);
|
1253
|
+
}
|
1169
1254
|
while (this.current !== null && this.isDigit(this.current)) {
|
1170
1255
|
value += this.current;
|
1171
1256
|
this.advance();
|
@@ -1377,6 +1462,9 @@ function createMethodCall(object, method, args) {
|
|
1377
1462
|
function createEnumAccess(enumName, value) {
|
1378
1463
|
return { kind: "EnumAccess", enumName, value };
|
1379
1464
|
}
|
1465
|
+
function createArrayLiteral(elements) {
|
1466
|
+
return { kind: "ArrayLiteral", elements };
|
1467
|
+
}
|
1380
1468
|
|
1381
1469
|
// src/expression/Parser.ts
|
1382
1470
|
var ExpressionParser = class {
|
@@ -1673,6 +1761,17 @@ var ExpressionParser = class {
|
|
1673
1761
|
this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
|
1674
1762
|
return expr;
|
1675
1763
|
}
|
1764
|
+
if (this.match("LBRACKET" /* LBRACKET */)) {
|
1765
|
+
const elements = [];
|
1766
|
+
if (this.current().type !== "RBRACKET" /* RBRACKET */) {
|
1767
|
+
elements.push(this.parseTernary());
|
1768
|
+
while (this.match("COMMA" /* COMMA */)) {
|
1769
|
+
elements.push(this.parseTernary());
|
1770
|
+
}
|
1771
|
+
}
|
1772
|
+
this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array literal");
|
1773
|
+
return createArrayLiteral(elements);
|
1774
|
+
}
|
1676
1775
|
throw new ParseError(
|
1677
1776
|
`Unexpected token: ${this.current().type}`,
|
1678
1777
|
this.current().position
|
@@ -1711,6 +1810,8 @@ var Evaluator = class {
|
|
1711
1810
|
return this.evaluateMethodCall(n.object, n.method, n.args, context);
|
1712
1811
|
case "EnumAccess":
|
1713
1812
|
return this.evaluateEnumAccess(n.enumName, n.value, context);
|
1813
|
+
case "ArrayLiteral":
|
1814
|
+
return this.evaluateArrayLiteral(n.elements, context);
|
1714
1815
|
default:
|
1715
1816
|
throw new ParseError(`Unknown AST node kind: ${node.kind}`);
|
1716
1817
|
}
|
@@ -1899,8 +2000,30 @@ var Evaluator = class {
|
|
1899
2000
|
if (typeof left === "bigint" || typeof right === "bigint") {
|
1900
2001
|
return BigInt(left) === BigInt(right);
|
1901
2002
|
}
|
2003
|
+
const toArray = (v) => {
|
2004
|
+
if (Array.isArray(v))
|
2005
|
+
return v.map((x) => typeof x === "bigint" ? Number(x) : x);
|
2006
|
+
if (v instanceof Uint8Array) return Array.from(v);
|
2007
|
+
return null;
|
2008
|
+
};
|
2009
|
+
const leftArr = toArray(left);
|
2010
|
+
const rightArr = toArray(right);
|
2011
|
+
if (leftArr && rightArr) {
|
2012
|
+
if (leftArr.length !== rightArr.length) return false;
|
2013
|
+
for (let i = 0; i < leftArr.length; i++) {
|
2014
|
+
if (leftArr[i] !== rightArr[i]) return false;
|
2015
|
+
}
|
2016
|
+
return true;
|
2017
|
+
}
|
1902
2018
|
return left === right;
|
1903
2019
|
}
|
2020
|
+
/**
|
2021
|
+
* Evaluate an array literal.
|
2022
|
+
* @private
|
2023
|
+
*/
|
2024
|
+
evaluateArrayLiteral(elements, context) {
|
2025
|
+
return elements.map((e) => this.evaluate(e, context));
|
2026
|
+
}
|
1904
2027
|
/**
|
1905
2028
|
* Helper: Convert to number.
|
1906
2029
|
* @private
|
@@ -1935,7 +2058,8 @@ var Evaluator = class {
|
|
1935
2058
|
|
1936
2059
|
// src/expression/index.ts
|
1937
2060
|
function evaluateExpression(expression, context) {
|
1938
|
-
const
|
2061
|
+
const preprocessed = expression.replace(/\.as<[^>]+>/g, "");
|
2062
|
+
const lexer = new Lexer(preprocessed);
|
1939
2063
|
const tokens = lexer.tokenize();
|
1940
2064
|
const parser = new ExpressionParser(tokens);
|
1941
2065
|
const ast = parser.parse();
|
@@ -1961,6 +2085,18 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1961
2085
|
throw new ParseError("Root schema must have meta.id");
|
1962
2086
|
}
|
1963
2087
|
}
|
2088
|
+
/**
|
2089
|
+
* Safely extract a KaitaiStream from an object that may expose `_io`.
|
2090
|
+
* Avoids using `any` casts to satisfy linting.
|
2091
|
+
*/
|
2092
|
+
static getKaitaiIO(val) {
|
2093
|
+
if (val && typeof val === "object") {
|
2094
|
+
const rec = val;
|
2095
|
+
const maybe = rec["_io"];
|
2096
|
+
if (maybe instanceof KaitaiStream) return maybe;
|
2097
|
+
}
|
2098
|
+
return null;
|
2099
|
+
}
|
1964
2100
|
/**
|
1965
2101
|
* Parse binary data according to the schema.
|
1966
2102
|
*
|
@@ -1973,6 +2109,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1973
2109
|
const result = {};
|
1974
2110
|
const context = new Context(stream, result, parent, this.schema.enums);
|
1975
2111
|
context.current = result;
|
2112
|
+
result["_io"] = stream;
|
1976
2113
|
if (typeArgs && this.schema.params) {
|
1977
2114
|
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
1978
2115
|
const param = this.schema.params[i];
|
@@ -2075,7 +2212,22 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2075
2212
|
* @private
|
2076
2213
|
*/
|
2077
2214
|
parseAttribute(attr, context) {
|
2078
|
-
|
2215
|
+
let stream = context.io;
|
2216
|
+
if (attr.io !== void 0) {
|
2217
|
+
const ioVal = this.evaluateValue(attr.io, context);
|
2218
|
+
if (ioVal instanceof KaitaiStream) {
|
2219
|
+
stream = ioVal;
|
2220
|
+
} else {
|
2221
|
+
const kio = _TypeInterpreter.getKaitaiIO(ioVal);
|
2222
|
+
if (kio) {
|
2223
|
+
stream = kio;
|
2224
|
+
} else {
|
2225
|
+
throw new ParseError(
|
2226
|
+
"io must evaluate to a KaitaiStream or an object with _io"
|
2227
|
+
);
|
2228
|
+
}
|
2229
|
+
}
|
2230
|
+
}
|
2079
2231
|
if (attr.if) {
|
2080
2232
|
const condition = this.evaluateValue(attr.if, context);
|
2081
2233
|
if (!condition) {
|
@@ -2092,9 +2244,6 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2092
2244
|
throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
|
2093
2245
|
}
|
2094
2246
|
}
|
2095
|
-
if (attr.io) {
|
2096
|
-
throw new NotImplementedError("Custom I/O streams");
|
2097
|
-
}
|
2098
2247
|
if (attr.repeat) {
|
2099
2248
|
return this.parseRepeated(attr, context);
|
2100
2249
|
}
|
@@ -2191,7 +2340,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2191
2340
|
}
|
2192
2341
|
return bytes;
|
2193
2342
|
} else {
|
2194
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2343
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2195
2344
|
const str = stream.readStr(expected.length, encoding);
|
2196
2345
|
if (str !== expected) {
|
2197
2346
|
throw new ValidationError(
|
@@ -2220,7 +2369,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2220
2369
|
throw new ParseError(`size must be non-negative, got ${size}`);
|
2221
2370
|
}
|
2222
2371
|
if (type === "str" || !type) {
|
2223
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2372
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2224
2373
|
let data;
|
2225
2374
|
if (type === "str") {
|
2226
2375
|
data = stream.readBytes(size);
|
@@ -2246,11 +2395,19 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2246
2395
|
}
|
2247
2396
|
if (attr["size-eos"]) {
|
2248
2397
|
if (type === "str") {
|
2249
|
-
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2398
|
+
const encoding = attr.encoding || this.schema.meta?.encoding || this.parentMeta?.encoding || "UTF-8";
|
2250
2399
|
const bytes = stream.readBytesFull();
|
2251
2400
|
return new TextDecoder(encoding).decode(bytes);
|
2252
2401
|
} else {
|
2253
|
-
|
2402
|
+
let bytes = stream.readBytesFull();
|
2403
|
+
if (attr.process) {
|
2404
|
+
bytes = this.applyProcessing(bytes, attr.process);
|
2405
|
+
}
|
2406
|
+
if (type) {
|
2407
|
+
const sub = new KaitaiStream(bytes);
|
2408
|
+
return this.parseType(type, sub, context, attr["type-args"]);
|
2409
|
+
}
|
2410
|
+
return bytes;
|
2254
2411
|
}
|
2255
2412
|
}
|
2256
2413
|
if (!type) {
|
@@ -2346,6 +2503,12 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2346
2503
|
if (isFloatType(type)) {
|
2347
2504
|
return this.readFloat(base, endian, stream);
|
2348
2505
|
}
|
2506
|
+
if (/^b\d+$/.test(type)) {
|
2507
|
+
const n = parseInt(type.slice(1), 10);
|
2508
|
+
const val = stream.readBitsIntBe(n);
|
2509
|
+
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
|
2510
|
+
return val <= maxSafe ? Number(val) : val;
|
2511
|
+
}
|
2349
2512
|
if (isStringType(type)) {
|
2350
2513
|
const encoding = this.schema.meta?.encoding || "UTF-8";
|
2351
2514
|
if (type === "strz") {
|