@k67/kaitai-struct-ts 0.2.0 → 0.3.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
@@ -866,17 +866,23 @@ var Context = class _Context {
866
866
  * @param _io - Binary stream being read
867
867
  * @param _root - Root object of the parse tree
868
868
  * @param _parent - Parent object (optional)
869
+ * @param enums - Enum definitions from schema (optional)
869
870
  */
870
- constructor(_io, _root = null, _parent = null) {
871
+ constructor(_io, _root = null, _parent = null, enums) {
871
872
  this._io = _io;
872
873
  this._root = _root;
873
874
  /** Stack of parent objects */
874
875
  this.parentStack = [];
875
876
  /** Current object being parsed */
876
877
  this._current = {};
878
+ /** Enum definitions from schema */
879
+ this._enums = {};
877
880
  if (_parent !== null) {
878
881
  this.parentStack.push(_parent);
879
882
  }
883
+ if (enums) {
884
+ this._enums = enums;
885
+ }
880
886
  }
881
887
  /**
882
888
  * Get the current I/O stream.
@@ -973,6 +979,36 @@ var Context = class _Context {
973
979
  set(name, value) {
974
980
  this._current[name] = value;
975
981
  }
982
+ /**
983
+ * Get enum value by name.
984
+ * Used for enum access in expressions (EnumName::value).
985
+ *
986
+ * @param enumName - Name of the enum
987
+ * @param valueName - Name of the enum value
988
+ * @returns Enum value (number) or undefined
989
+ */
990
+ getEnumValue(enumName, valueName) {
991
+ const enumDef = this._enums[enumName];
992
+ if (!enumDef) {
993
+ return void 0;
994
+ }
995
+ for (const [key, value] of Object.entries(enumDef)) {
996
+ if (value === valueName) {
997
+ const numKey = Number(key);
998
+ return isNaN(numKey) ? key : numKey;
999
+ }
1000
+ }
1001
+ return void 0;
1002
+ }
1003
+ /**
1004
+ * Check if an enum exists.
1005
+ *
1006
+ * @param enumName - Name of the enum
1007
+ * @returns True if enum exists
1008
+ */
1009
+ hasEnum(enumName) {
1010
+ return enumName in this._enums;
1011
+ }
976
1012
  /**
977
1013
  * Create a child context for nested parsing.
978
1014
  * The current object becomes the parent in the child context.
@@ -984,7 +1020,8 @@ var Context = class _Context {
984
1020
  const childContext = new _Context(
985
1021
  stream || this._io,
986
1022
  this._root || this._current,
987
- this._current
1023
+ this._current,
1024
+ this._enums
988
1025
  );
989
1026
  return childContext;
990
1027
  }
@@ -995,13 +1032,919 @@ var Context = class _Context {
995
1032
  * @returns Cloned context
996
1033
  */
997
1034
  clone() {
998
- const cloned = new _Context(this._io, this._root, this.parent);
1035
+ const cloned = new _Context(this._io, this._root, this.parent, this._enums);
999
1036
  cloned._current = { ...this._current };
1000
1037
  cloned.parentStack = [...this.parentStack];
1001
1038
  return cloned;
1002
1039
  }
1003
1040
  };
1004
1041
 
1042
+ // src/expression/Token.ts
1043
+ function createToken(type, value = null, position = 0) {
1044
+ return { type, value, position };
1045
+ }
1046
+
1047
+ // src/expression/Lexer.ts
1048
+ var Lexer = class {
1049
+ /**
1050
+ * Create a new lexer.
1051
+ *
1052
+ * @param input - Expression string to tokenize
1053
+ */
1054
+ constructor(input) {
1055
+ this.position = 0;
1056
+ this.current = null;
1057
+ this.input = input;
1058
+ this.current = input.length > 0 ? input[0] : null;
1059
+ }
1060
+ /**
1061
+ * Tokenize the entire input string.
1062
+ *
1063
+ * @returns Array of tokens
1064
+ * @throws {ParseError} If invalid syntax is encountered
1065
+ */
1066
+ tokenize() {
1067
+ const tokens = [];
1068
+ while (this.current !== null) {
1069
+ if (this.isWhitespace(this.current)) {
1070
+ this.skipWhitespace();
1071
+ continue;
1072
+ }
1073
+ if (this.isDigit(this.current)) {
1074
+ tokens.push(this.readNumber());
1075
+ continue;
1076
+ }
1077
+ if (this.isIdentifierStart(this.current)) {
1078
+ tokens.push(this.readIdentifierOrKeyword());
1079
+ continue;
1080
+ }
1081
+ if (this.current === '"' || this.current === "'") {
1082
+ tokens.push(this.readString());
1083
+ continue;
1084
+ }
1085
+ const token = this.readOperator();
1086
+ if (token) {
1087
+ tokens.push(token);
1088
+ continue;
1089
+ }
1090
+ throw new ParseError(
1091
+ `Unexpected character: ${this.current}`,
1092
+ this.position
1093
+ );
1094
+ }
1095
+ tokens.push(createToken("EOF" /* EOF */, null, this.position));
1096
+ return tokens;
1097
+ }
1098
+ /**
1099
+ * Advance to the next character.
1100
+ * @private
1101
+ */
1102
+ advance() {
1103
+ this.position++;
1104
+ this.current = this.position < this.input.length ? this.input[this.position] : null;
1105
+ }
1106
+ /**
1107
+ * Peek at the next character without advancing.
1108
+ * @private
1109
+ */
1110
+ peek(offset = 1) {
1111
+ const pos = this.position + offset;
1112
+ return pos < this.input.length ? this.input[pos] : null;
1113
+ }
1114
+ /**
1115
+ * Check if character is whitespace.
1116
+ * @private
1117
+ */
1118
+ isWhitespace(char) {
1119
+ return /\s/.test(char);
1120
+ }
1121
+ /**
1122
+ * Check if character is a digit.
1123
+ * @private
1124
+ */
1125
+ isDigit(char) {
1126
+ return /[0-9]/.test(char);
1127
+ }
1128
+ /**
1129
+ * Check if character can start an identifier.
1130
+ * @private
1131
+ */
1132
+ isIdentifierStart(char) {
1133
+ return /[a-zA-Z_]/.test(char);
1134
+ }
1135
+ /**
1136
+ * Check if character can be part of an identifier.
1137
+ * @private
1138
+ */
1139
+ isIdentifierPart(char) {
1140
+ return /[a-zA-Z0-9_]/.test(char);
1141
+ }
1142
+ /**
1143
+ * Skip whitespace characters.
1144
+ * @private
1145
+ */
1146
+ skipWhitespace() {
1147
+ while (this.current !== null && this.isWhitespace(this.current)) {
1148
+ this.advance();
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Read a number token.
1153
+ * @private
1154
+ */
1155
+ readNumber() {
1156
+ const start = this.position;
1157
+ let value = "";
1158
+ if (this.current === "0" && this.peek() === "x") {
1159
+ value += this.current;
1160
+ this.advance();
1161
+ value += this.current;
1162
+ this.advance();
1163
+ while (this.current !== null && /[0-9a-fA-F]/.test(this.current)) {
1164
+ value += this.current;
1165
+ this.advance();
1166
+ }
1167
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
1168
+ }
1169
+ while (this.current !== null && this.isDigit(this.current)) {
1170
+ value += this.current;
1171
+ this.advance();
1172
+ }
1173
+ if (this.current === "." && this.peek() && this.isDigit(this.peek())) {
1174
+ value += this.current;
1175
+ this.advance();
1176
+ while (this.current !== null && this.isDigit(this.current)) {
1177
+ value += this.current;
1178
+ this.advance();
1179
+ }
1180
+ return createToken("NUMBER" /* NUMBER */, parseFloat(value), start);
1181
+ }
1182
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 10), start);
1183
+ }
1184
+ /**
1185
+ * Read an identifier or keyword token.
1186
+ * @private
1187
+ */
1188
+ readIdentifierOrKeyword() {
1189
+ const start = this.position;
1190
+ let value = "";
1191
+ while (this.current !== null && this.isIdentifierPart(this.current)) {
1192
+ value += this.current;
1193
+ this.advance();
1194
+ }
1195
+ switch (value) {
1196
+ case "true":
1197
+ return createToken("BOOLEAN" /* BOOLEAN */, true, start);
1198
+ case "false":
1199
+ return createToken("BOOLEAN" /* BOOLEAN */, false, start);
1200
+ case "and":
1201
+ return createToken("AND" /* AND */, value, start);
1202
+ case "or":
1203
+ return createToken("OR" /* OR */, value, start);
1204
+ case "not":
1205
+ return createToken("NOT" /* NOT */, value, start);
1206
+ default:
1207
+ return createToken("IDENTIFIER" /* IDENTIFIER */, value, start);
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Read a string token.
1212
+ * @private
1213
+ */
1214
+ readString() {
1215
+ const start = this.position;
1216
+ const quote = this.current;
1217
+ let value = "";
1218
+ this.advance();
1219
+ while (this.current !== null && this.current !== quote) {
1220
+ if (this.current === "\\") {
1221
+ this.advance();
1222
+ if (this.current === null) {
1223
+ throw new ParseError("Unterminated string", start);
1224
+ }
1225
+ const ch = this.current;
1226
+ if (ch === "n") {
1227
+ value += "\n";
1228
+ } else if (ch === "t") {
1229
+ value += " ";
1230
+ } else if (ch === "r") {
1231
+ value += "\r";
1232
+ } else if (ch === "\\") {
1233
+ value += "\\";
1234
+ } else if (ch === '"') {
1235
+ value += '"';
1236
+ } else if (ch === "'") {
1237
+ value += "'";
1238
+ } else {
1239
+ value += ch;
1240
+ }
1241
+ } else {
1242
+ value += this.current;
1243
+ }
1244
+ this.advance();
1245
+ }
1246
+ if (this.current === null) {
1247
+ throw new ParseError("Unterminated string", start);
1248
+ }
1249
+ this.advance();
1250
+ return createToken("STRING" /* STRING */, value, start);
1251
+ }
1252
+ /**
1253
+ * Read an operator or punctuation token.
1254
+ * @private
1255
+ */
1256
+ readOperator() {
1257
+ const start = this.position;
1258
+ const char = this.current;
1259
+ switch (char) {
1260
+ case "+":
1261
+ this.advance();
1262
+ return createToken("PLUS" /* PLUS */, char, start);
1263
+ case "-":
1264
+ this.advance();
1265
+ return createToken("MINUS" /* MINUS */, char, start);
1266
+ case "*":
1267
+ this.advance();
1268
+ return createToken("STAR" /* STAR */, char, start);
1269
+ case "/":
1270
+ this.advance();
1271
+ return createToken("SLASH" /* SLASH */, char, start);
1272
+ case "%":
1273
+ this.advance();
1274
+ return createToken("PERCENT" /* PERCENT */, char, start);
1275
+ case "<":
1276
+ this.advance();
1277
+ if (this.current === "=") {
1278
+ this.advance();
1279
+ return createToken("LE" /* LE */, "<=", start);
1280
+ } else if (this.current === "<") {
1281
+ this.advance();
1282
+ return createToken("LSHIFT" /* LSHIFT */, "<<", start);
1283
+ }
1284
+ return createToken("LT" /* LT */, "<", start);
1285
+ case ">":
1286
+ this.advance();
1287
+ if (this.current === "=") {
1288
+ this.advance();
1289
+ return createToken("GE" /* GE */, ">=", start);
1290
+ } else if (this.current === ">") {
1291
+ this.advance();
1292
+ return createToken("RSHIFT" /* RSHIFT */, ">>", start);
1293
+ }
1294
+ return createToken("GT" /* GT */, ">", start);
1295
+ case "=":
1296
+ this.advance();
1297
+ if (this.current === "=") {
1298
+ this.advance();
1299
+ return createToken("EQ" /* EQ */, "==", start);
1300
+ }
1301
+ throw new ParseError("Expected == for equality", start);
1302
+ case "!":
1303
+ this.advance();
1304
+ if (this.current === "=") {
1305
+ this.advance();
1306
+ return createToken("NE" /* NE */, "!=", start);
1307
+ }
1308
+ throw new ParseError("Expected != for inequality", start);
1309
+ case "&":
1310
+ this.advance();
1311
+ return createToken("AMPERSAND" /* AMPERSAND */, char, start);
1312
+ case "|":
1313
+ this.advance();
1314
+ return createToken("PIPE" /* PIPE */, char, start);
1315
+ case "^":
1316
+ this.advance();
1317
+ return createToken("CARET" /* CARET */, char, start);
1318
+ case "?":
1319
+ this.advance();
1320
+ return createToken("QUESTION" /* QUESTION */, char, start);
1321
+ case ":":
1322
+ this.advance();
1323
+ if (this.current === ":") {
1324
+ this.advance();
1325
+ return createToken("DOUBLE_COLON" /* DOUBLE_COLON */, "::", start);
1326
+ }
1327
+ return createToken("COLON" /* COLON */, char, start);
1328
+ case "(":
1329
+ this.advance();
1330
+ return createToken("LPAREN" /* LPAREN */, char, start);
1331
+ case ")":
1332
+ this.advance();
1333
+ return createToken("RPAREN" /* RPAREN */, char, start);
1334
+ case "[":
1335
+ this.advance();
1336
+ return createToken("LBRACKET" /* LBRACKET */, char, start);
1337
+ case "]":
1338
+ this.advance();
1339
+ return createToken("RBRACKET" /* RBRACKET */, char, start);
1340
+ case ".":
1341
+ this.advance();
1342
+ return createToken("DOT" /* DOT */, char, start);
1343
+ case ",":
1344
+ this.advance();
1345
+ return createToken("COMMA" /* COMMA */, char, start);
1346
+ default:
1347
+ return null;
1348
+ }
1349
+ }
1350
+ };
1351
+
1352
+ // src/expression/AST.ts
1353
+ function createLiteral(value) {
1354
+ return { kind: "Literal", value };
1355
+ }
1356
+ function createIdentifier(name) {
1357
+ return { kind: "Identifier", name };
1358
+ }
1359
+ function createBinaryOp(operator, left, right) {
1360
+ return { kind: "BinaryOp", operator, left, right };
1361
+ }
1362
+ function createUnaryOp(operator, operand) {
1363
+ return { kind: "UnaryOp", operator, operand };
1364
+ }
1365
+ function createTernary(condition, ifTrue, ifFalse) {
1366
+ return { kind: "Ternary", condition, ifTrue, ifFalse };
1367
+ }
1368
+ function createMemberAccess(object, property) {
1369
+ return { kind: "MemberAccess", object, property };
1370
+ }
1371
+ function createIndexAccess(object, index) {
1372
+ return { kind: "IndexAccess", object, index };
1373
+ }
1374
+ function createMethodCall(object, method, args) {
1375
+ return { kind: "MethodCall", object, method, args };
1376
+ }
1377
+ function createEnumAccess(enumName, value) {
1378
+ return { kind: "EnumAccess", enumName, value };
1379
+ }
1380
+
1381
+ // src/expression/Parser.ts
1382
+ var ExpressionParser = class {
1383
+ /**
1384
+ * Create a new expression parser.
1385
+ *
1386
+ * @param tokens - Array of tokens to parse
1387
+ */
1388
+ constructor(tokens) {
1389
+ this.position = 0;
1390
+ this.tokens = tokens;
1391
+ }
1392
+ /**
1393
+ * Parse the tokens into an AST.
1394
+ *
1395
+ * @returns Root AST node
1396
+ * @throws {ParseError} If invalid syntax is encountered
1397
+ */
1398
+ parse() {
1399
+ const expr = this.parseTernary();
1400
+ if (!this.isAtEnd()) {
1401
+ throw new ParseError(
1402
+ `Unexpected token: ${this.current().type}`,
1403
+ this.current().position
1404
+ );
1405
+ }
1406
+ return expr;
1407
+ }
1408
+ /**
1409
+ * Get the current token.
1410
+ * @private
1411
+ */
1412
+ current() {
1413
+ return this.tokens[this.position];
1414
+ }
1415
+ /**
1416
+ * Check if we're at the end of tokens.
1417
+ * @private
1418
+ */
1419
+ isAtEnd() {
1420
+ return this.current().type === "EOF" /* EOF */;
1421
+ }
1422
+ /**
1423
+ * Advance to the next token.
1424
+ * @private
1425
+ */
1426
+ advance() {
1427
+ if (!this.isAtEnd()) {
1428
+ this.position++;
1429
+ }
1430
+ return this.tokens[this.position - 1];
1431
+ }
1432
+ /**
1433
+ * Check if current token matches any of the given types.
1434
+ * @private
1435
+ */
1436
+ match(...types) {
1437
+ for (const type of types) {
1438
+ if (this.current().type === type) {
1439
+ this.advance();
1440
+ return true;
1441
+ }
1442
+ }
1443
+ return false;
1444
+ }
1445
+ /**
1446
+ * Expect a specific token type and advance.
1447
+ * @private
1448
+ */
1449
+ expect(type, message) {
1450
+ if (this.current().type !== type) {
1451
+ throw new ParseError(message, this.current().position);
1452
+ }
1453
+ return this.advance();
1454
+ }
1455
+ /**
1456
+ * Parse ternary conditional (lowest precedence).
1457
+ * condition ? ifTrue : ifFalse
1458
+ * @private
1459
+ */
1460
+ parseTernary() {
1461
+ let expr = this.parseLogicalOr();
1462
+ if (this.match("QUESTION" /* QUESTION */)) {
1463
+ const ifTrue = this.parseTernary();
1464
+ this.expect("COLON" /* COLON */, "Expected : in ternary expression");
1465
+ const ifFalse = this.parseTernary();
1466
+ return createTernary(expr, ifTrue, ifFalse);
1467
+ }
1468
+ return expr;
1469
+ }
1470
+ /**
1471
+ * Parse logical OR.
1472
+ * @private
1473
+ */
1474
+ parseLogicalOr() {
1475
+ let left = this.parseLogicalAnd();
1476
+ while (this.match("OR" /* OR */)) {
1477
+ const operator = "or";
1478
+ const right = this.parseLogicalAnd();
1479
+ left = createBinaryOp(operator, left, right);
1480
+ }
1481
+ return left;
1482
+ }
1483
+ /**
1484
+ * Parse logical AND.
1485
+ * @private
1486
+ */
1487
+ parseLogicalAnd() {
1488
+ let left = this.parseBitwiseOr();
1489
+ while (this.match("AND" /* AND */)) {
1490
+ const operator = "and";
1491
+ const right = this.parseBitwiseOr();
1492
+ left = createBinaryOp(operator, left, right);
1493
+ }
1494
+ return left;
1495
+ }
1496
+ /**
1497
+ * Parse bitwise OR.
1498
+ * @private
1499
+ */
1500
+ parseBitwiseOr() {
1501
+ let left = this.parseBitwiseXor();
1502
+ while (this.match("PIPE" /* PIPE */)) {
1503
+ const operator = "|";
1504
+ const right = this.parseBitwiseXor();
1505
+ left = createBinaryOp(operator, left, right);
1506
+ }
1507
+ return left;
1508
+ }
1509
+ /**
1510
+ * Parse bitwise XOR.
1511
+ * @private
1512
+ */
1513
+ parseBitwiseXor() {
1514
+ let left = this.parseBitwiseAnd();
1515
+ while (this.match("CARET" /* CARET */)) {
1516
+ const operator = "^";
1517
+ const right = this.parseBitwiseAnd();
1518
+ left = createBinaryOp(operator, left, right);
1519
+ }
1520
+ return left;
1521
+ }
1522
+ /**
1523
+ * Parse bitwise AND.
1524
+ * @private
1525
+ */
1526
+ parseBitwiseAnd() {
1527
+ let left = this.parseEquality();
1528
+ while (this.match("AMPERSAND" /* AMPERSAND */)) {
1529
+ const operator = "&";
1530
+ const right = this.parseEquality();
1531
+ left = createBinaryOp(operator, left, right);
1532
+ }
1533
+ return left;
1534
+ }
1535
+ /**
1536
+ * Parse equality operators (==, !=).
1537
+ * @private
1538
+ */
1539
+ parseEquality() {
1540
+ let left = this.parseRelational();
1541
+ while (this.match("EQ" /* EQ */, "NE" /* NE */)) {
1542
+ const operator = this.tokens[this.position - 1].value;
1543
+ const right = this.parseRelational();
1544
+ left = createBinaryOp(operator, left, right);
1545
+ }
1546
+ return left;
1547
+ }
1548
+ /**
1549
+ * Parse relational operators (<, <=, >, >=).
1550
+ * @private
1551
+ */
1552
+ parseRelational() {
1553
+ let left = this.parseBitShift();
1554
+ while (this.match("LT" /* LT */, "LE" /* LE */, "GT" /* GT */, "GE" /* GE */)) {
1555
+ const operator = this.tokens[this.position - 1].value;
1556
+ const right = this.parseBitShift();
1557
+ left = createBinaryOp(operator, left, right);
1558
+ }
1559
+ return left;
1560
+ }
1561
+ /**
1562
+ * Parse bit shift operators (<<, >>).
1563
+ * @private
1564
+ */
1565
+ parseBitShift() {
1566
+ let left = this.parseAdditive();
1567
+ while (this.match("LSHIFT" /* LSHIFT */, "RSHIFT" /* RSHIFT */)) {
1568
+ const operator = this.tokens[this.position - 1].value;
1569
+ const right = this.parseAdditive();
1570
+ left = createBinaryOp(operator, left, right);
1571
+ }
1572
+ return left;
1573
+ }
1574
+ /**
1575
+ * Parse additive operators (+, -).
1576
+ * @private
1577
+ */
1578
+ parseAdditive() {
1579
+ let left = this.parseMultiplicative();
1580
+ while (this.match("PLUS" /* PLUS */, "MINUS" /* MINUS */)) {
1581
+ const operator = this.tokens[this.position - 1].value;
1582
+ const right = this.parseMultiplicative();
1583
+ left = createBinaryOp(operator, left, right);
1584
+ }
1585
+ return left;
1586
+ }
1587
+ /**
1588
+ * Parse multiplicative operators (*, /, %).
1589
+ * @private
1590
+ */
1591
+ parseMultiplicative() {
1592
+ let left = this.parseUnary();
1593
+ while (this.match("STAR" /* STAR */, "SLASH" /* SLASH */, "PERCENT" /* PERCENT */)) {
1594
+ const operator = this.tokens[this.position - 1].value;
1595
+ const right = this.parseUnary();
1596
+ left = createBinaryOp(operator, left, right);
1597
+ }
1598
+ return left;
1599
+ }
1600
+ /**
1601
+ * Parse unary operators (-, not).
1602
+ * @private
1603
+ */
1604
+ parseUnary() {
1605
+ if (this.match("MINUS" /* MINUS */, "NOT" /* NOT */)) {
1606
+ const operator = this.tokens[this.position - 1].value;
1607
+ const operand = this.parseUnary();
1608
+ return createUnaryOp(operator, operand);
1609
+ }
1610
+ return this.parsePostfix();
1611
+ }
1612
+ /**
1613
+ * Parse postfix operators (., [], ()).
1614
+ * @private
1615
+ */
1616
+ parsePostfix() {
1617
+ let expr = this.parsePrimary();
1618
+ while (true) {
1619
+ if (this.match("DOT" /* DOT */)) {
1620
+ const property = this.expect(
1621
+ "IDENTIFIER" /* IDENTIFIER */,
1622
+ "Expected property name after ."
1623
+ );
1624
+ expr = createMemberAccess(expr, property.value);
1625
+ } else if (this.match("LBRACKET" /* LBRACKET */)) {
1626
+ const index = this.parseTernary();
1627
+ this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array index");
1628
+ expr = createIndexAccess(expr, index);
1629
+ } else if (this.current().type === "LPAREN" /* LPAREN */) {
1630
+ if (expr.kind === "MemberAccess") {
1631
+ this.advance();
1632
+ const args = [];
1633
+ if (this.current().type !== "RPAREN" /* RPAREN */) {
1634
+ args.push(this.parseTernary());
1635
+ while (this.match("COMMA" /* COMMA */)) {
1636
+ args.push(this.parseTernary());
1637
+ }
1638
+ }
1639
+ this.expect("RPAREN" /* RPAREN */, "Expected ) after arguments");
1640
+ const memberExpr = expr;
1641
+ expr = createMethodCall(memberExpr.object, memberExpr.property, args);
1642
+ } else {
1643
+ break;
1644
+ }
1645
+ } else {
1646
+ break;
1647
+ }
1648
+ }
1649
+ return expr;
1650
+ }
1651
+ /**
1652
+ * Parse primary expressions (literals, identifiers, grouping).
1653
+ * @private
1654
+ */
1655
+ parsePrimary() {
1656
+ if (this.match("NUMBER" /* NUMBER */, "STRING" /* STRING */, "BOOLEAN" /* BOOLEAN */)) {
1657
+ const token = this.tokens[this.position - 1];
1658
+ return createLiteral(token.value);
1659
+ }
1660
+ if (this.match("IDENTIFIER" /* IDENTIFIER */)) {
1661
+ const name = this.tokens[this.position - 1].value;
1662
+ if (this.match("DOUBLE_COLON" /* DOUBLE_COLON */)) {
1663
+ const value = this.expect(
1664
+ "IDENTIFIER" /* IDENTIFIER */,
1665
+ "Expected enum value after ::"
1666
+ );
1667
+ return createEnumAccess(name, value.value);
1668
+ }
1669
+ return createIdentifier(name);
1670
+ }
1671
+ if (this.match("LPAREN" /* LPAREN */)) {
1672
+ const expr = this.parseTernary();
1673
+ this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
1674
+ return expr;
1675
+ }
1676
+ throw new ParseError(
1677
+ `Unexpected token: ${this.current().type}`,
1678
+ this.current().position
1679
+ );
1680
+ }
1681
+ };
1682
+
1683
+ // src/expression/Evaluator.ts
1684
+ var Evaluator = class {
1685
+ /**
1686
+ * Evaluate an AST node in the given context.
1687
+ *
1688
+ * @param node - AST node to evaluate
1689
+ * @param context - Execution context
1690
+ * @returns Evaluated value
1691
+ * @throws {ParseError} If evaluation fails
1692
+ */
1693
+ evaluate(node, context) {
1694
+ const n = node;
1695
+ switch (node.kind) {
1696
+ case "Literal":
1697
+ return n.value;
1698
+ case "Identifier":
1699
+ return this.evaluateIdentifier(n.name, context);
1700
+ case "BinaryOp":
1701
+ return this.evaluateBinaryOp(n.operator, n.left, n.right, context);
1702
+ case "UnaryOp":
1703
+ return this.evaluateUnaryOp(n.operator, n.operand, context);
1704
+ case "Ternary":
1705
+ return this.evaluateTernary(n.condition, n.ifTrue, n.ifFalse, context);
1706
+ case "MemberAccess":
1707
+ return this.evaluateMemberAccess(n.object, n.property, context);
1708
+ case "IndexAccess":
1709
+ return this.evaluateIndexAccess(n.object, n.index, context);
1710
+ case "MethodCall":
1711
+ return this.evaluateMethodCall(n.object, n.method, n.args, context);
1712
+ case "EnumAccess":
1713
+ return this.evaluateEnumAccess(n.enumName, n.value, context);
1714
+ default:
1715
+ throw new ParseError(`Unknown AST node kind: ${node.kind}`);
1716
+ }
1717
+ }
1718
+ /**
1719
+ * Evaluate an identifier.
1720
+ * @private
1721
+ */
1722
+ evaluateIdentifier(name, context) {
1723
+ return context.resolve(name);
1724
+ }
1725
+ /**
1726
+ * Evaluate a binary operation.
1727
+ * @private
1728
+ */
1729
+ evaluateBinaryOp(operator, left, right, context) {
1730
+ const leftVal = this.evaluate(left, context);
1731
+ const rightVal = this.evaluate(right, context);
1732
+ switch (operator) {
1733
+ // Arithmetic
1734
+ case "+":
1735
+ return this.add(leftVal, rightVal);
1736
+ case "-":
1737
+ return this.toNumber(leftVal) - this.toNumber(rightVal);
1738
+ case "*":
1739
+ return this.toNumber(leftVal) * this.toNumber(rightVal);
1740
+ case "/":
1741
+ return this.toNumber(leftVal) / this.toNumber(rightVal);
1742
+ case "%":
1743
+ return this.modulo(this.toNumber(leftVal), this.toNumber(rightVal));
1744
+ // Comparison
1745
+ case "<":
1746
+ return this.compare(leftVal, rightVal) < 0;
1747
+ case "<=":
1748
+ return this.compare(leftVal, rightVal) <= 0;
1749
+ case ">":
1750
+ return this.compare(leftVal, rightVal) > 0;
1751
+ case ">=":
1752
+ return this.compare(leftVal, rightVal) >= 0;
1753
+ case "==":
1754
+ return this.equals(leftVal, rightVal);
1755
+ case "!=":
1756
+ return !this.equals(leftVal, rightVal);
1757
+ // Bitwise
1758
+ case "<<":
1759
+ return this.toInt(leftVal) << this.toInt(rightVal);
1760
+ case ">>":
1761
+ return this.toInt(leftVal) >> this.toInt(rightVal);
1762
+ case "&":
1763
+ return this.toInt(leftVal) & this.toInt(rightVal);
1764
+ case "|":
1765
+ return this.toInt(leftVal) | this.toInt(rightVal);
1766
+ case "^":
1767
+ return this.toInt(leftVal) ^ this.toInt(rightVal);
1768
+ // Logical
1769
+ case "and":
1770
+ return this.toBoolean(leftVal) && this.toBoolean(rightVal);
1771
+ case "or":
1772
+ return this.toBoolean(leftVal) || this.toBoolean(rightVal);
1773
+ default:
1774
+ throw new ParseError(`Unknown binary operator: ${operator}`);
1775
+ }
1776
+ }
1777
+ /**
1778
+ * Evaluate a unary operation.
1779
+ * @private
1780
+ */
1781
+ evaluateUnaryOp(operator, operand, context) {
1782
+ const value = this.evaluate(operand, context);
1783
+ switch (operator) {
1784
+ case "-":
1785
+ return -this.toNumber(value);
1786
+ case "not":
1787
+ return !this.toBoolean(value);
1788
+ default:
1789
+ throw new ParseError(`Unknown unary operator: ${operator}`);
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Evaluate a ternary conditional.
1794
+ * @private
1795
+ */
1796
+ evaluateTernary(condition, ifTrue, ifFalse, context) {
1797
+ const condValue = this.evaluate(condition, context);
1798
+ return this.toBoolean(condValue) ? this.evaluate(ifTrue, context) : this.evaluate(ifFalse, context);
1799
+ }
1800
+ /**
1801
+ * Evaluate member access (object.property).
1802
+ * @private
1803
+ */
1804
+ evaluateMemberAccess(object, property, context) {
1805
+ const obj = this.evaluate(object, context);
1806
+ if (obj === null || obj === void 0) {
1807
+ throw new ParseError(
1808
+ `Cannot access property ${property} of null/undefined`
1809
+ );
1810
+ }
1811
+ if (typeof obj === "object") {
1812
+ return obj[property];
1813
+ }
1814
+ throw new ParseError(`Cannot access property ${property} of non-object`);
1815
+ }
1816
+ /**
1817
+ * Evaluate index access (array[index]).
1818
+ * @private
1819
+ */
1820
+ evaluateIndexAccess(object, index, context) {
1821
+ const obj = this.evaluate(object, context);
1822
+ const idx = this.evaluate(index, context);
1823
+ if (Array.isArray(obj)) {
1824
+ const numIdx = this.toInt(idx);
1825
+ return obj[numIdx];
1826
+ }
1827
+ if (obj instanceof Uint8Array) {
1828
+ const numIdx = this.toInt(idx);
1829
+ return obj[numIdx];
1830
+ }
1831
+ throw new ParseError("Index access requires an array");
1832
+ }
1833
+ /**
1834
+ * Evaluate method call (object.method()).
1835
+ * @private
1836
+ */
1837
+ evaluateMethodCall(object, method, _args, context) {
1838
+ const obj = this.evaluate(object, context);
1839
+ if (method === "length" || method === "size") {
1840
+ if (Array.isArray(obj)) return obj.length;
1841
+ if (obj instanceof Uint8Array) return obj.length;
1842
+ if (typeof obj === "string") return obj.length;
1843
+ throw new ParseError(`Object does not have a ${method} property`);
1844
+ }
1845
+ if (method === "to_i") {
1846
+ return this.toInt(obj);
1847
+ }
1848
+ if (method === "to_s") {
1849
+ return String(obj);
1850
+ }
1851
+ throw new ParseError(`Unknown method: ${method}`);
1852
+ }
1853
+ /**
1854
+ * Evaluate enum access (EnumName::value).
1855
+ * @private
1856
+ */
1857
+ evaluateEnumAccess(enumName, valueName, context) {
1858
+ const value = context.getEnumValue(enumName, valueName);
1859
+ if (value === void 0) {
1860
+ throw new ParseError(
1861
+ `Enum value "${enumName}::${valueName}" not found`
1862
+ );
1863
+ }
1864
+ return value;
1865
+ }
1866
+ /**
1867
+ * Helper: Add two values (handles strings and numbers).
1868
+ * @private
1869
+ */
1870
+ add(left, right) {
1871
+ if (typeof left === "string" || typeof right === "string") {
1872
+ return String(left) + String(right);
1873
+ }
1874
+ return this.toNumber(left) + this.toNumber(right);
1875
+ }
1876
+ /**
1877
+ * Helper: Modulo operation (Kaitai-style, not remainder).
1878
+ * @private
1879
+ */
1880
+ modulo(a, b) {
1881
+ const result = a % b;
1882
+ return result < 0 ? result + b : result;
1883
+ }
1884
+ /**
1885
+ * Helper: Compare two values.
1886
+ * @private
1887
+ */
1888
+ compare(left, right) {
1889
+ if (typeof left === "string" && typeof right === "string") {
1890
+ return left < right ? -1 : left > right ? 1 : 0;
1891
+ }
1892
+ const leftNum = this.toNumber(left);
1893
+ const rightNum = this.toNumber(right);
1894
+ return leftNum < rightNum ? -1 : leftNum > rightNum ? 1 : 0;
1895
+ }
1896
+ /**
1897
+ * Helper: Check equality.
1898
+ * @private
1899
+ */
1900
+ equals(left, right) {
1901
+ if (typeof left === "bigint" || typeof right === "bigint") {
1902
+ return BigInt(left) === BigInt(right);
1903
+ }
1904
+ return left === right;
1905
+ }
1906
+ /**
1907
+ * Helper: Convert to number.
1908
+ * @private
1909
+ */
1910
+ toNumber(value) {
1911
+ if (typeof value === "number") return value;
1912
+ if (typeof value === "bigint") return Number(value);
1913
+ if (typeof value === "boolean") return value ? 1 : 0;
1914
+ if (typeof value === "string") return parseFloat(value);
1915
+ throw new ParseError(`Cannot convert ${typeof value} to number`);
1916
+ }
1917
+ /**
1918
+ * Helper: Convert to integer.
1919
+ * @private
1920
+ */
1921
+ toInt(value) {
1922
+ return Math.floor(this.toNumber(value));
1923
+ }
1924
+ /**
1925
+ * Helper: Convert to boolean.
1926
+ * @private
1927
+ */
1928
+ toBoolean(value) {
1929
+ if (typeof value === "boolean") return value;
1930
+ if (typeof value === "number") return value !== 0;
1931
+ if (typeof value === "bigint") return value !== 0n;
1932
+ if (typeof value === "string") return value.length > 0;
1933
+ if (value === null || value === void 0) return false;
1934
+ return true;
1935
+ }
1936
+ };
1937
+
1938
+ // src/expression/index.ts
1939
+ function evaluateExpression(expression, context) {
1940
+ const lexer = new Lexer(expression);
1941
+ const tokens = lexer.tokenize();
1942
+ const parser = new ExpressionParser(tokens);
1943
+ const ast = parser.parse();
1944
+ const evaluator = new Evaluator();
1945
+ return evaluator.evaluate(ast, context);
1946
+ }
1947
+
1005
1948
  // src/interpreter/TypeInterpreter.ts
1006
1949
  var TypeInterpreter = class _TypeInterpreter {
1007
1950
  /**
@@ -1029,7 +1972,7 @@ var TypeInterpreter = class _TypeInterpreter {
1029
1972
  */
1030
1973
  parse(stream, parent) {
1031
1974
  const result = {};
1032
- const context = new Context(stream, result, parent);
1975
+ const context = new Context(stream, result, parent, this.schema.enums);
1033
1976
  context.current = result;
1034
1977
  if (this.schema.seq) {
1035
1978
  for (const attr of this.schema.seq) {
@@ -1052,11 +1995,20 @@ var TypeInterpreter = class _TypeInterpreter {
1052
1995
  parseAttribute(attr, context) {
1053
1996
  const stream = context.io;
1054
1997
  if (attr.if) {
1055
- throw new NotImplementedError("Conditional parsing (if)");
1998
+ const condition = this.evaluateValue(attr.if, context);
1999
+ if (!condition) {
2000
+ return void 0;
2001
+ }
1056
2002
  }
1057
2003
  if (attr.pos !== void 0) {
1058
- const pos = typeof attr.pos === "number" ? attr.pos : 0;
1059
- stream.seek(pos);
2004
+ const pos = this.evaluateValue(attr.pos, context);
2005
+ if (typeof pos === "number") {
2006
+ stream.seek(pos);
2007
+ } else if (typeof pos === "bigint") {
2008
+ stream.seek(Number(pos));
2009
+ } else {
2010
+ throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
2011
+ }
1060
2012
  }
1061
2013
  if (attr.io) {
1062
2014
  throw new NotImplementedError("Custom I/O streams");
@@ -1067,7 +2019,8 @@ var TypeInterpreter = class _TypeInterpreter {
1067
2019
  if (attr.contents) {
1068
2020
  return this.parseContents(attr, context);
1069
2021
  }
1070
- return this.parseValue(attr, context);
2022
+ const value = this.parseValue(attr, context);
2023
+ return value;
1071
2024
  }
1072
2025
  /**
1073
2026
  * Parse a repeated attribute.
@@ -1078,14 +2031,22 @@ var TypeInterpreter = class _TypeInterpreter {
1078
2031
  * @private
1079
2032
  */
1080
2033
  parseRepeated(attr, context) {
1081
- const result = [];
1082
2034
  const stream = context.io;
2035
+ const result = [];
1083
2036
  switch (attr.repeat) {
1084
2037
  case "expr": {
1085
- const count = typeof attr["repeat-expr"] === "number" ? attr["repeat-expr"] : 0;
2038
+ const countValue = this.evaluateValue(attr["repeat-expr"], context);
2039
+ const count = typeof countValue === "number" ? countValue : typeof countValue === "bigint" ? Number(countValue) : 0;
2040
+ if (count < 0) {
2041
+ throw new ParseError(`repeat-expr must be non-negative, got ${count}`);
2042
+ }
1086
2043
  for (let i = 0; i < count; i++) {
1087
2044
  context.set("_index", i);
1088
- result.push(this.parseValue(attr, context));
2045
+ const value = this.parseAttribute(
2046
+ { ...attr, repeat: void 0, "repeat-expr": void 0 },
2047
+ context
2048
+ );
2049
+ result.push(value);
1089
2050
  }
1090
2051
  break;
1091
2052
  }
@@ -1100,7 +2061,25 @@ var TypeInterpreter = class _TypeInterpreter {
1100
2061
  if (!attr["repeat-until"]) {
1101
2062
  throw new ParseError("repeat-until expression is required");
1102
2063
  }
1103
- throw new NotImplementedError("repeat-until");
2064
+ let index = 0;
2065
+ while (true) {
2066
+ context.set("_index", index);
2067
+ const value = this.parseAttribute(
2068
+ { ...attr, repeat: void 0, "repeat-until": void 0 },
2069
+ context
2070
+ );
2071
+ result.push(value);
2072
+ context.set("_", value);
2073
+ const condition = this.evaluateValue(attr["repeat-until"], context);
2074
+ if (condition) {
2075
+ break;
2076
+ }
2077
+ if (stream.isEof()) {
2078
+ break;
2079
+ }
2080
+ index++;
2081
+ }
2082
+ break;
1104
2083
  }
1105
2084
  default:
1106
2085
  throw new ParseError(`Unknown repeat type: ${attr.repeat}`);
@@ -1153,7 +2132,11 @@ var TypeInterpreter = class _TypeInterpreter {
1153
2132
  const stream = context.io;
1154
2133
  const type = attr.type;
1155
2134
  if (attr.size !== void 0) {
1156
- const size = typeof attr.size === "number" ? attr.size : 0;
2135
+ const sizeValue = this.evaluateValue(attr.size, context);
2136
+ const size = typeof sizeValue === "number" ? sizeValue : typeof sizeValue === "bigint" ? Number(sizeValue) : 0;
2137
+ if (size < 0) {
2138
+ throw new ParseError(`size must be non-negative, got ${size}`);
2139
+ }
1157
2140
  if (type === "str" || !type) {
1158
2141
  const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
1159
2142
  if (type === "str") {
@@ -1199,6 +2182,9 @@ var TypeInterpreter = class _TypeInterpreter {
1199
2182
  if (this.schema.types && type in this.schema.types) {
1200
2183
  const typeSchema = this.schema.types[type];
1201
2184
  const meta = this.schema.meta || this.parentMeta;
2185
+ if (this.schema.enums && !typeSchema.enums) {
2186
+ typeSchema.enums = this.schema.enums;
2187
+ }
1202
2188
  const interpreter = new _TypeInterpreter(typeSchema, meta);
1203
2189
  return interpreter.parse(stream, context.current);
1204
2190
  }
@@ -1280,6 +2266,34 @@ var TypeInterpreter = class _TypeInterpreter {
1280
2266
  throw new ParseError(`Unknown float type: ${type}`);
1281
2267
  }
1282
2268
  }
2269
+ /**
2270
+ * Evaluate an expression or return a literal value.
2271
+ * If the value is a string, it's treated as an expression.
2272
+ * If it's a number or boolean, it's returned as-is.
2273
+ *
2274
+ * @param value - Expression string or literal value
2275
+ * @param context - Execution context
2276
+ * @returns Evaluated result
2277
+ * @private
2278
+ */
2279
+ evaluateValue(value, context) {
2280
+ if (value === void 0) {
2281
+ return void 0;
2282
+ }
2283
+ if (typeof value === "number" || typeof value === "boolean") {
2284
+ return value;
2285
+ }
2286
+ if (typeof value === "string") {
2287
+ try {
2288
+ return evaluateExpression(value, context);
2289
+ } catch (error) {
2290
+ throw new ParseError(
2291
+ `Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
2292
+ );
2293
+ }
2294
+ }
2295
+ return value;
2296
+ }
1283
2297
  };
1284
2298
 
1285
2299
  // src/index.ts
@@ -1359,6 +2373,42 @@ function parse(ksyYaml, buffer, options = {}) {
1359
2373
  * @author Fabiano Pinto
1360
2374
  * @license MIT
1361
2375
  */
2376
+ /**
2377
+ * @fileoverview Token types for Kaitai Struct expression language
2378
+ * @module expression/Token
2379
+ * @author Fabiano Pinto
2380
+ * @license MIT
2381
+ */
2382
+ /**
2383
+ * @fileoverview Lexer for Kaitai Struct expression language
2384
+ * @module expression/Lexer
2385
+ * @author Fabiano Pinto
2386
+ * @license MIT
2387
+ */
2388
+ /**
2389
+ * @fileoverview Abstract Syntax Tree nodes for expression language
2390
+ * @module expression/AST
2391
+ * @author Fabiano Pinto
2392
+ * @license MIT
2393
+ */
2394
+ /**
2395
+ * @fileoverview Parser for Kaitai Struct expression language
2396
+ * @module expression/Parser
2397
+ * @author Fabiano Pinto
2398
+ * @license MIT
2399
+ */
2400
+ /**
2401
+ * @fileoverview Evaluator for Kaitai Struct expression AST
2402
+ * @module expression/Evaluator
2403
+ * @author Fabiano Pinto
2404
+ * @license MIT
2405
+ */
2406
+ /**
2407
+ * @fileoverview Expression evaluation module
2408
+ * @module expression
2409
+ * @author Fabiano Pinto
2410
+ * @license MIT
2411
+ */
1362
2412
  /**
1363
2413
  * @fileoverview Type interpreter for executing Kaitai Struct schemas
1364
2414
  * @module interpreter/TypeInterpreter