@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/dist/index.mjs CHANGED
@@ -549,7 +549,9 @@ var BUILTIN_TYPES = [
549
549
  "strz"
550
550
  ];
551
551
  function isBuiltinType(type) {
552
- return BUILTIN_TYPES.includes(type);
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
- parseWithImports(mainYaml, _imports, options = {}) {
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 lexer = new Lexer(expression);
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
- const stream = context.io;
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
- return stream.readBytesFull();
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
- throw new ParseError("String types require size, size-eos, or terminator");
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
  }