@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.mjs CHANGED
@@ -824,17 +824,23 @@ var Context = class _Context {
824
824
  * @param _io - Binary stream being read
825
825
  * @param _root - Root object of the parse tree
826
826
  * @param _parent - Parent object (optional)
827
+ * @param enums - Enum definitions from schema (optional)
827
828
  */
828
- constructor(_io, _root = null, _parent = null) {
829
+ constructor(_io, _root = null, _parent = null, enums) {
829
830
  this._io = _io;
830
831
  this._root = _root;
831
832
  /** Stack of parent objects */
832
833
  this.parentStack = [];
833
834
  /** Current object being parsed */
834
835
  this._current = {};
836
+ /** Enum definitions from schema */
837
+ this._enums = {};
835
838
  if (_parent !== null) {
836
839
  this.parentStack.push(_parent);
837
840
  }
841
+ if (enums) {
842
+ this._enums = enums;
843
+ }
838
844
  }
839
845
  /**
840
846
  * Get the current I/O stream.
@@ -931,6 +937,36 @@ var Context = class _Context {
931
937
  set(name, value) {
932
938
  this._current[name] = value;
933
939
  }
940
+ /**
941
+ * Get enum value by name.
942
+ * Used for enum access in expressions (EnumName::value).
943
+ *
944
+ * @param enumName - Name of the enum
945
+ * @param valueName - Name of the enum value
946
+ * @returns Enum value (number) or undefined
947
+ */
948
+ getEnumValue(enumName, valueName) {
949
+ const enumDef = this._enums[enumName];
950
+ if (!enumDef) {
951
+ return void 0;
952
+ }
953
+ for (const [key, value] of Object.entries(enumDef)) {
954
+ if (value === valueName) {
955
+ const numKey = Number(key);
956
+ return isNaN(numKey) ? key : numKey;
957
+ }
958
+ }
959
+ return void 0;
960
+ }
961
+ /**
962
+ * Check if an enum exists.
963
+ *
964
+ * @param enumName - Name of the enum
965
+ * @returns True if enum exists
966
+ */
967
+ hasEnum(enumName) {
968
+ return enumName in this._enums;
969
+ }
934
970
  /**
935
971
  * Create a child context for nested parsing.
936
972
  * The current object becomes the parent in the child context.
@@ -942,7 +978,8 @@ var Context = class _Context {
942
978
  const childContext = new _Context(
943
979
  stream || this._io,
944
980
  this._root || this._current,
945
- this._current
981
+ this._current,
982
+ this._enums
946
983
  );
947
984
  return childContext;
948
985
  }
@@ -953,13 +990,919 @@ var Context = class _Context {
953
990
  * @returns Cloned context
954
991
  */
955
992
  clone() {
956
- const cloned = new _Context(this._io, this._root, this.parent);
993
+ const cloned = new _Context(this._io, this._root, this.parent, this._enums);
957
994
  cloned._current = { ...this._current };
958
995
  cloned.parentStack = [...this.parentStack];
959
996
  return cloned;
960
997
  }
961
998
  };
962
999
 
1000
+ // src/expression/Token.ts
1001
+ function createToken(type, value = null, position = 0) {
1002
+ return { type, value, position };
1003
+ }
1004
+
1005
+ // src/expression/Lexer.ts
1006
+ var Lexer = class {
1007
+ /**
1008
+ * Create a new lexer.
1009
+ *
1010
+ * @param input - Expression string to tokenize
1011
+ */
1012
+ constructor(input) {
1013
+ this.position = 0;
1014
+ this.current = null;
1015
+ this.input = input;
1016
+ this.current = input.length > 0 ? input[0] : null;
1017
+ }
1018
+ /**
1019
+ * Tokenize the entire input string.
1020
+ *
1021
+ * @returns Array of tokens
1022
+ * @throws {ParseError} If invalid syntax is encountered
1023
+ */
1024
+ tokenize() {
1025
+ const tokens = [];
1026
+ while (this.current !== null) {
1027
+ if (this.isWhitespace(this.current)) {
1028
+ this.skipWhitespace();
1029
+ continue;
1030
+ }
1031
+ if (this.isDigit(this.current)) {
1032
+ tokens.push(this.readNumber());
1033
+ continue;
1034
+ }
1035
+ if (this.isIdentifierStart(this.current)) {
1036
+ tokens.push(this.readIdentifierOrKeyword());
1037
+ continue;
1038
+ }
1039
+ if (this.current === '"' || this.current === "'") {
1040
+ tokens.push(this.readString());
1041
+ continue;
1042
+ }
1043
+ const token = this.readOperator();
1044
+ if (token) {
1045
+ tokens.push(token);
1046
+ continue;
1047
+ }
1048
+ throw new ParseError(
1049
+ `Unexpected character: ${this.current}`,
1050
+ this.position
1051
+ );
1052
+ }
1053
+ tokens.push(createToken("EOF" /* EOF */, null, this.position));
1054
+ return tokens;
1055
+ }
1056
+ /**
1057
+ * Advance to the next character.
1058
+ * @private
1059
+ */
1060
+ advance() {
1061
+ this.position++;
1062
+ this.current = this.position < this.input.length ? this.input[this.position] : null;
1063
+ }
1064
+ /**
1065
+ * Peek at the next character without advancing.
1066
+ * @private
1067
+ */
1068
+ peek(offset = 1) {
1069
+ const pos = this.position + offset;
1070
+ return pos < this.input.length ? this.input[pos] : null;
1071
+ }
1072
+ /**
1073
+ * Check if character is whitespace.
1074
+ * @private
1075
+ */
1076
+ isWhitespace(char) {
1077
+ return /\s/.test(char);
1078
+ }
1079
+ /**
1080
+ * Check if character is a digit.
1081
+ * @private
1082
+ */
1083
+ isDigit(char) {
1084
+ return /[0-9]/.test(char);
1085
+ }
1086
+ /**
1087
+ * Check if character can start an identifier.
1088
+ * @private
1089
+ */
1090
+ isIdentifierStart(char) {
1091
+ return /[a-zA-Z_]/.test(char);
1092
+ }
1093
+ /**
1094
+ * Check if character can be part of an identifier.
1095
+ * @private
1096
+ */
1097
+ isIdentifierPart(char) {
1098
+ return /[a-zA-Z0-9_]/.test(char);
1099
+ }
1100
+ /**
1101
+ * Skip whitespace characters.
1102
+ * @private
1103
+ */
1104
+ skipWhitespace() {
1105
+ while (this.current !== null && this.isWhitespace(this.current)) {
1106
+ this.advance();
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Read a number token.
1111
+ * @private
1112
+ */
1113
+ readNumber() {
1114
+ const start = this.position;
1115
+ let value = "";
1116
+ if (this.current === "0" && this.peek() === "x") {
1117
+ value += this.current;
1118
+ this.advance();
1119
+ value += this.current;
1120
+ this.advance();
1121
+ while (this.current !== null && /[0-9a-fA-F]/.test(this.current)) {
1122
+ value += this.current;
1123
+ this.advance();
1124
+ }
1125
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
1126
+ }
1127
+ while (this.current !== null && this.isDigit(this.current)) {
1128
+ value += this.current;
1129
+ this.advance();
1130
+ }
1131
+ if (this.current === "." && this.peek() && this.isDigit(this.peek())) {
1132
+ value += this.current;
1133
+ this.advance();
1134
+ while (this.current !== null && this.isDigit(this.current)) {
1135
+ value += this.current;
1136
+ this.advance();
1137
+ }
1138
+ return createToken("NUMBER" /* NUMBER */, parseFloat(value), start);
1139
+ }
1140
+ return createToken("NUMBER" /* NUMBER */, parseInt(value, 10), start);
1141
+ }
1142
+ /**
1143
+ * Read an identifier or keyword token.
1144
+ * @private
1145
+ */
1146
+ readIdentifierOrKeyword() {
1147
+ const start = this.position;
1148
+ let value = "";
1149
+ while (this.current !== null && this.isIdentifierPart(this.current)) {
1150
+ value += this.current;
1151
+ this.advance();
1152
+ }
1153
+ switch (value) {
1154
+ case "true":
1155
+ return createToken("BOOLEAN" /* BOOLEAN */, true, start);
1156
+ case "false":
1157
+ return createToken("BOOLEAN" /* BOOLEAN */, false, start);
1158
+ case "and":
1159
+ return createToken("AND" /* AND */, value, start);
1160
+ case "or":
1161
+ return createToken("OR" /* OR */, value, start);
1162
+ case "not":
1163
+ return createToken("NOT" /* NOT */, value, start);
1164
+ default:
1165
+ return createToken("IDENTIFIER" /* IDENTIFIER */, value, start);
1166
+ }
1167
+ }
1168
+ /**
1169
+ * Read a string token.
1170
+ * @private
1171
+ */
1172
+ readString() {
1173
+ const start = this.position;
1174
+ const quote = this.current;
1175
+ let value = "";
1176
+ this.advance();
1177
+ while (this.current !== null && this.current !== quote) {
1178
+ if (this.current === "\\") {
1179
+ this.advance();
1180
+ if (this.current === null) {
1181
+ throw new ParseError("Unterminated string", start);
1182
+ }
1183
+ const ch = this.current;
1184
+ if (ch === "n") {
1185
+ value += "\n";
1186
+ } else if (ch === "t") {
1187
+ value += " ";
1188
+ } else if (ch === "r") {
1189
+ value += "\r";
1190
+ } else if (ch === "\\") {
1191
+ value += "\\";
1192
+ } else if (ch === '"') {
1193
+ value += '"';
1194
+ } else if (ch === "'") {
1195
+ value += "'";
1196
+ } else {
1197
+ value += ch;
1198
+ }
1199
+ } else {
1200
+ value += this.current;
1201
+ }
1202
+ this.advance();
1203
+ }
1204
+ if (this.current === null) {
1205
+ throw new ParseError("Unterminated string", start);
1206
+ }
1207
+ this.advance();
1208
+ return createToken("STRING" /* STRING */, value, start);
1209
+ }
1210
+ /**
1211
+ * Read an operator or punctuation token.
1212
+ * @private
1213
+ */
1214
+ readOperator() {
1215
+ const start = this.position;
1216
+ const char = this.current;
1217
+ switch (char) {
1218
+ case "+":
1219
+ this.advance();
1220
+ return createToken("PLUS" /* PLUS */, char, start);
1221
+ case "-":
1222
+ this.advance();
1223
+ return createToken("MINUS" /* MINUS */, char, start);
1224
+ case "*":
1225
+ this.advance();
1226
+ return createToken("STAR" /* STAR */, char, start);
1227
+ case "/":
1228
+ this.advance();
1229
+ return createToken("SLASH" /* SLASH */, char, start);
1230
+ case "%":
1231
+ this.advance();
1232
+ return createToken("PERCENT" /* PERCENT */, char, start);
1233
+ case "<":
1234
+ this.advance();
1235
+ if (this.current === "=") {
1236
+ this.advance();
1237
+ return createToken("LE" /* LE */, "<=", start);
1238
+ } else if (this.current === "<") {
1239
+ this.advance();
1240
+ return createToken("LSHIFT" /* LSHIFT */, "<<", start);
1241
+ }
1242
+ return createToken("LT" /* LT */, "<", start);
1243
+ case ">":
1244
+ this.advance();
1245
+ if (this.current === "=") {
1246
+ this.advance();
1247
+ return createToken("GE" /* GE */, ">=", start);
1248
+ } else if (this.current === ">") {
1249
+ this.advance();
1250
+ return createToken("RSHIFT" /* RSHIFT */, ">>", start);
1251
+ }
1252
+ return createToken("GT" /* GT */, ">", start);
1253
+ case "=":
1254
+ this.advance();
1255
+ if (this.current === "=") {
1256
+ this.advance();
1257
+ return createToken("EQ" /* EQ */, "==", start);
1258
+ }
1259
+ throw new ParseError("Expected == for equality", start);
1260
+ case "!":
1261
+ this.advance();
1262
+ if (this.current === "=") {
1263
+ this.advance();
1264
+ return createToken("NE" /* NE */, "!=", start);
1265
+ }
1266
+ throw new ParseError("Expected != for inequality", start);
1267
+ case "&":
1268
+ this.advance();
1269
+ return createToken("AMPERSAND" /* AMPERSAND */, char, start);
1270
+ case "|":
1271
+ this.advance();
1272
+ return createToken("PIPE" /* PIPE */, char, start);
1273
+ case "^":
1274
+ this.advance();
1275
+ return createToken("CARET" /* CARET */, char, start);
1276
+ case "?":
1277
+ this.advance();
1278
+ return createToken("QUESTION" /* QUESTION */, char, start);
1279
+ case ":":
1280
+ this.advance();
1281
+ if (this.current === ":") {
1282
+ this.advance();
1283
+ return createToken("DOUBLE_COLON" /* DOUBLE_COLON */, "::", start);
1284
+ }
1285
+ return createToken("COLON" /* COLON */, char, start);
1286
+ case "(":
1287
+ this.advance();
1288
+ return createToken("LPAREN" /* LPAREN */, char, start);
1289
+ case ")":
1290
+ this.advance();
1291
+ return createToken("RPAREN" /* RPAREN */, char, start);
1292
+ case "[":
1293
+ this.advance();
1294
+ return createToken("LBRACKET" /* LBRACKET */, char, start);
1295
+ case "]":
1296
+ this.advance();
1297
+ return createToken("RBRACKET" /* RBRACKET */, char, start);
1298
+ case ".":
1299
+ this.advance();
1300
+ return createToken("DOT" /* DOT */, char, start);
1301
+ case ",":
1302
+ this.advance();
1303
+ return createToken("COMMA" /* COMMA */, char, start);
1304
+ default:
1305
+ return null;
1306
+ }
1307
+ }
1308
+ };
1309
+
1310
+ // src/expression/AST.ts
1311
+ function createLiteral(value) {
1312
+ return { kind: "Literal", value };
1313
+ }
1314
+ function createIdentifier(name) {
1315
+ return { kind: "Identifier", name };
1316
+ }
1317
+ function createBinaryOp(operator, left, right) {
1318
+ return { kind: "BinaryOp", operator, left, right };
1319
+ }
1320
+ function createUnaryOp(operator, operand) {
1321
+ return { kind: "UnaryOp", operator, operand };
1322
+ }
1323
+ function createTernary(condition, ifTrue, ifFalse) {
1324
+ return { kind: "Ternary", condition, ifTrue, ifFalse };
1325
+ }
1326
+ function createMemberAccess(object, property) {
1327
+ return { kind: "MemberAccess", object, property };
1328
+ }
1329
+ function createIndexAccess(object, index) {
1330
+ return { kind: "IndexAccess", object, index };
1331
+ }
1332
+ function createMethodCall(object, method, args) {
1333
+ return { kind: "MethodCall", object, method, args };
1334
+ }
1335
+ function createEnumAccess(enumName, value) {
1336
+ return { kind: "EnumAccess", enumName, value };
1337
+ }
1338
+
1339
+ // src/expression/Parser.ts
1340
+ var ExpressionParser = class {
1341
+ /**
1342
+ * Create a new expression parser.
1343
+ *
1344
+ * @param tokens - Array of tokens to parse
1345
+ */
1346
+ constructor(tokens) {
1347
+ this.position = 0;
1348
+ this.tokens = tokens;
1349
+ }
1350
+ /**
1351
+ * Parse the tokens into an AST.
1352
+ *
1353
+ * @returns Root AST node
1354
+ * @throws {ParseError} If invalid syntax is encountered
1355
+ */
1356
+ parse() {
1357
+ const expr = this.parseTernary();
1358
+ if (!this.isAtEnd()) {
1359
+ throw new ParseError(
1360
+ `Unexpected token: ${this.current().type}`,
1361
+ this.current().position
1362
+ );
1363
+ }
1364
+ return expr;
1365
+ }
1366
+ /**
1367
+ * Get the current token.
1368
+ * @private
1369
+ */
1370
+ current() {
1371
+ return this.tokens[this.position];
1372
+ }
1373
+ /**
1374
+ * Check if we're at the end of tokens.
1375
+ * @private
1376
+ */
1377
+ isAtEnd() {
1378
+ return this.current().type === "EOF" /* EOF */;
1379
+ }
1380
+ /**
1381
+ * Advance to the next token.
1382
+ * @private
1383
+ */
1384
+ advance() {
1385
+ if (!this.isAtEnd()) {
1386
+ this.position++;
1387
+ }
1388
+ return this.tokens[this.position - 1];
1389
+ }
1390
+ /**
1391
+ * Check if current token matches any of the given types.
1392
+ * @private
1393
+ */
1394
+ match(...types) {
1395
+ for (const type of types) {
1396
+ if (this.current().type === type) {
1397
+ this.advance();
1398
+ return true;
1399
+ }
1400
+ }
1401
+ return false;
1402
+ }
1403
+ /**
1404
+ * Expect a specific token type and advance.
1405
+ * @private
1406
+ */
1407
+ expect(type, message) {
1408
+ if (this.current().type !== type) {
1409
+ throw new ParseError(message, this.current().position);
1410
+ }
1411
+ return this.advance();
1412
+ }
1413
+ /**
1414
+ * Parse ternary conditional (lowest precedence).
1415
+ * condition ? ifTrue : ifFalse
1416
+ * @private
1417
+ */
1418
+ parseTernary() {
1419
+ let expr = this.parseLogicalOr();
1420
+ if (this.match("QUESTION" /* QUESTION */)) {
1421
+ const ifTrue = this.parseTernary();
1422
+ this.expect("COLON" /* COLON */, "Expected : in ternary expression");
1423
+ const ifFalse = this.parseTernary();
1424
+ return createTernary(expr, ifTrue, ifFalse);
1425
+ }
1426
+ return expr;
1427
+ }
1428
+ /**
1429
+ * Parse logical OR.
1430
+ * @private
1431
+ */
1432
+ parseLogicalOr() {
1433
+ let left = this.parseLogicalAnd();
1434
+ while (this.match("OR" /* OR */)) {
1435
+ const operator = "or";
1436
+ const right = this.parseLogicalAnd();
1437
+ left = createBinaryOp(operator, left, right);
1438
+ }
1439
+ return left;
1440
+ }
1441
+ /**
1442
+ * Parse logical AND.
1443
+ * @private
1444
+ */
1445
+ parseLogicalAnd() {
1446
+ let left = this.parseBitwiseOr();
1447
+ while (this.match("AND" /* AND */)) {
1448
+ const operator = "and";
1449
+ const right = this.parseBitwiseOr();
1450
+ left = createBinaryOp(operator, left, right);
1451
+ }
1452
+ return left;
1453
+ }
1454
+ /**
1455
+ * Parse bitwise OR.
1456
+ * @private
1457
+ */
1458
+ parseBitwiseOr() {
1459
+ let left = this.parseBitwiseXor();
1460
+ while (this.match("PIPE" /* PIPE */)) {
1461
+ const operator = "|";
1462
+ const right = this.parseBitwiseXor();
1463
+ left = createBinaryOp(operator, left, right);
1464
+ }
1465
+ return left;
1466
+ }
1467
+ /**
1468
+ * Parse bitwise XOR.
1469
+ * @private
1470
+ */
1471
+ parseBitwiseXor() {
1472
+ let left = this.parseBitwiseAnd();
1473
+ while (this.match("CARET" /* CARET */)) {
1474
+ const operator = "^";
1475
+ const right = this.parseBitwiseAnd();
1476
+ left = createBinaryOp(operator, left, right);
1477
+ }
1478
+ return left;
1479
+ }
1480
+ /**
1481
+ * Parse bitwise AND.
1482
+ * @private
1483
+ */
1484
+ parseBitwiseAnd() {
1485
+ let left = this.parseEquality();
1486
+ while (this.match("AMPERSAND" /* AMPERSAND */)) {
1487
+ const operator = "&";
1488
+ const right = this.parseEquality();
1489
+ left = createBinaryOp(operator, left, right);
1490
+ }
1491
+ return left;
1492
+ }
1493
+ /**
1494
+ * Parse equality operators (==, !=).
1495
+ * @private
1496
+ */
1497
+ parseEquality() {
1498
+ let left = this.parseRelational();
1499
+ while (this.match("EQ" /* EQ */, "NE" /* NE */)) {
1500
+ const operator = this.tokens[this.position - 1].value;
1501
+ const right = this.parseRelational();
1502
+ left = createBinaryOp(operator, left, right);
1503
+ }
1504
+ return left;
1505
+ }
1506
+ /**
1507
+ * Parse relational operators (<, <=, >, >=).
1508
+ * @private
1509
+ */
1510
+ parseRelational() {
1511
+ let left = this.parseBitShift();
1512
+ while (this.match("LT" /* LT */, "LE" /* LE */, "GT" /* GT */, "GE" /* GE */)) {
1513
+ const operator = this.tokens[this.position - 1].value;
1514
+ const right = this.parseBitShift();
1515
+ left = createBinaryOp(operator, left, right);
1516
+ }
1517
+ return left;
1518
+ }
1519
+ /**
1520
+ * Parse bit shift operators (<<, >>).
1521
+ * @private
1522
+ */
1523
+ parseBitShift() {
1524
+ let left = this.parseAdditive();
1525
+ while (this.match("LSHIFT" /* LSHIFT */, "RSHIFT" /* RSHIFT */)) {
1526
+ const operator = this.tokens[this.position - 1].value;
1527
+ const right = this.parseAdditive();
1528
+ left = createBinaryOp(operator, left, right);
1529
+ }
1530
+ return left;
1531
+ }
1532
+ /**
1533
+ * Parse additive operators (+, -).
1534
+ * @private
1535
+ */
1536
+ parseAdditive() {
1537
+ let left = this.parseMultiplicative();
1538
+ while (this.match("PLUS" /* PLUS */, "MINUS" /* MINUS */)) {
1539
+ const operator = this.tokens[this.position - 1].value;
1540
+ const right = this.parseMultiplicative();
1541
+ left = createBinaryOp(operator, left, right);
1542
+ }
1543
+ return left;
1544
+ }
1545
+ /**
1546
+ * Parse multiplicative operators (*, /, %).
1547
+ * @private
1548
+ */
1549
+ parseMultiplicative() {
1550
+ let left = this.parseUnary();
1551
+ while (this.match("STAR" /* STAR */, "SLASH" /* SLASH */, "PERCENT" /* PERCENT */)) {
1552
+ const operator = this.tokens[this.position - 1].value;
1553
+ const right = this.parseUnary();
1554
+ left = createBinaryOp(operator, left, right);
1555
+ }
1556
+ return left;
1557
+ }
1558
+ /**
1559
+ * Parse unary operators (-, not).
1560
+ * @private
1561
+ */
1562
+ parseUnary() {
1563
+ if (this.match("MINUS" /* MINUS */, "NOT" /* NOT */)) {
1564
+ const operator = this.tokens[this.position - 1].value;
1565
+ const operand = this.parseUnary();
1566
+ return createUnaryOp(operator, operand);
1567
+ }
1568
+ return this.parsePostfix();
1569
+ }
1570
+ /**
1571
+ * Parse postfix operators (., [], ()).
1572
+ * @private
1573
+ */
1574
+ parsePostfix() {
1575
+ let expr = this.parsePrimary();
1576
+ while (true) {
1577
+ if (this.match("DOT" /* DOT */)) {
1578
+ const property = this.expect(
1579
+ "IDENTIFIER" /* IDENTIFIER */,
1580
+ "Expected property name after ."
1581
+ );
1582
+ expr = createMemberAccess(expr, property.value);
1583
+ } else if (this.match("LBRACKET" /* LBRACKET */)) {
1584
+ const index = this.parseTernary();
1585
+ this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array index");
1586
+ expr = createIndexAccess(expr, index);
1587
+ } else if (this.current().type === "LPAREN" /* LPAREN */) {
1588
+ if (expr.kind === "MemberAccess") {
1589
+ this.advance();
1590
+ const args = [];
1591
+ if (this.current().type !== "RPAREN" /* RPAREN */) {
1592
+ args.push(this.parseTernary());
1593
+ while (this.match("COMMA" /* COMMA */)) {
1594
+ args.push(this.parseTernary());
1595
+ }
1596
+ }
1597
+ this.expect("RPAREN" /* RPAREN */, "Expected ) after arguments");
1598
+ const memberExpr = expr;
1599
+ expr = createMethodCall(memberExpr.object, memberExpr.property, args);
1600
+ } else {
1601
+ break;
1602
+ }
1603
+ } else {
1604
+ break;
1605
+ }
1606
+ }
1607
+ return expr;
1608
+ }
1609
+ /**
1610
+ * Parse primary expressions (literals, identifiers, grouping).
1611
+ * @private
1612
+ */
1613
+ parsePrimary() {
1614
+ if (this.match("NUMBER" /* NUMBER */, "STRING" /* STRING */, "BOOLEAN" /* BOOLEAN */)) {
1615
+ const token = this.tokens[this.position - 1];
1616
+ return createLiteral(token.value);
1617
+ }
1618
+ if (this.match("IDENTIFIER" /* IDENTIFIER */)) {
1619
+ const name = this.tokens[this.position - 1].value;
1620
+ if (this.match("DOUBLE_COLON" /* DOUBLE_COLON */)) {
1621
+ const value = this.expect(
1622
+ "IDENTIFIER" /* IDENTIFIER */,
1623
+ "Expected enum value after ::"
1624
+ );
1625
+ return createEnumAccess(name, value.value);
1626
+ }
1627
+ return createIdentifier(name);
1628
+ }
1629
+ if (this.match("LPAREN" /* LPAREN */)) {
1630
+ const expr = this.parseTernary();
1631
+ this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
1632
+ return expr;
1633
+ }
1634
+ throw new ParseError(
1635
+ `Unexpected token: ${this.current().type}`,
1636
+ this.current().position
1637
+ );
1638
+ }
1639
+ };
1640
+
1641
+ // src/expression/Evaluator.ts
1642
+ var Evaluator = class {
1643
+ /**
1644
+ * Evaluate an AST node in the given context.
1645
+ *
1646
+ * @param node - AST node to evaluate
1647
+ * @param context - Execution context
1648
+ * @returns Evaluated value
1649
+ * @throws {ParseError} If evaluation fails
1650
+ */
1651
+ evaluate(node, context) {
1652
+ const n = node;
1653
+ switch (node.kind) {
1654
+ case "Literal":
1655
+ return n.value;
1656
+ case "Identifier":
1657
+ return this.evaluateIdentifier(n.name, context);
1658
+ case "BinaryOp":
1659
+ return this.evaluateBinaryOp(n.operator, n.left, n.right, context);
1660
+ case "UnaryOp":
1661
+ return this.evaluateUnaryOp(n.operator, n.operand, context);
1662
+ case "Ternary":
1663
+ return this.evaluateTernary(n.condition, n.ifTrue, n.ifFalse, context);
1664
+ case "MemberAccess":
1665
+ return this.evaluateMemberAccess(n.object, n.property, context);
1666
+ case "IndexAccess":
1667
+ return this.evaluateIndexAccess(n.object, n.index, context);
1668
+ case "MethodCall":
1669
+ return this.evaluateMethodCall(n.object, n.method, n.args, context);
1670
+ case "EnumAccess":
1671
+ return this.evaluateEnumAccess(n.enumName, n.value, context);
1672
+ default:
1673
+ throw new ParseError(`Unknown AST node kind: ${node.kind}`);
1674
+ }
1675
+ }
1676
+ /**
1677
+ * Evaluate an identifier.
1678
+ * @private
1679
+ */
1680
+ evaluateIdentifier(name, context) {
1681
+ return context.resolve(name);
1682
+ }
1683
+ /**
1684
+ * Evaluate a binary operation.
1685
+ * @private
1686
+ */
1687
+ evaluateBinaryOp(operator, left, right, context) {
1688
+ const leftVal = this.evaluate(left, context);
1689
+ const rightVal = this.evaluate(right, context);
1690
+ switch (operator) {
1691
+ // Arithmetic
1692
+ case "+":
1693
+ return this.add(leftVal, rightVal);
1694
+ case "-":
1695
+ return this.toNumber(leftVal) - this.toNumber(rightVal);
1696
+ case "*":
1697
+ return this.toNumber(leftVal) * this.toNumber(rightVal);
1698
+ case "/":
1699
+ return this.toNumber(leftVal) / this.toNumber(rightVal);
1700
+ case "%":
1701
+ return this.modulo(this.toNumber(leftVal), this.toNumber(rightVal));
1702
+ // Comparison
1703
+ case "<":
1704
+ return this.compare(leftVal, rightVal) < 0;
1705
+ case "<=":
1706
+ return this.compare(leftVal, rightVal) <= 0;
1707
+ case ">":
1708
+ return this.compare(leftVal, rightVal) > 0;
1709
+ case ">=":
1710
+ return this.compare(leftVal, rightVal) >= 0;
1711
+ case "==":
1712
+ return this.equals(leftVal, rightVal);
1713
+ case "!=":
1714
+ return !this.equals(leftVal, rightVal);
1715
+ // Bitwise
1716
+ case "<<":
1717
+ return this.toInt(leftVal) << this.toInt(rightVal);
1718
+ case ">>":
1719
+ return this.toInt(leftVal) >> this.toInt(rightVal);
1720
+ case "&":
1721
+ return this.toInt(leftVal) & this.toInt(rightVal);
1722
+ case "|":
1723
+ return this.toInt(leftVal) | this.toInt(rightVal);
1724
+ case "^":
1725
+ return this.toInt(leftVal) ^ this.toInt(rightVal);
1726
+ // Logical
1727
+ case "and":
1728
+ return this.toBoolean(leftVal) && this.toBoolean(rightVal);
1729
+ case "or":
1730
+ return this.toBoolean(leftVal) || this.toBoolean(rightVal);
1731
+ default:
1732
+ throw new ParseError(`Unknown binary operator: ${operator}`);
1733
+ }
1734
+ }
1735
+ /**
1736
+ * Evaluate a unary operation.
1737
+ * @private
1738
+ */
1739
+ evaluateUnaryOp(operator, operand, context) {
1740
+ const value = this.evaluate(operand, context);
1741
+ switch (operator) {
1742
+ case "-":
1743
+ return -this.toNumber(value);
1744
+ case "not":
1745
+ return !this.toBoolean(value);
1746
+ default:
1747
+ throw new ParseError(`Unknown unary operator: ${operator}`);
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Evaluate a ternary conditional.
1752
+ * @private
1753
+ */
1754
+ evaluateTernary(condition, ifTrue, ifFalse, context) {
1755
+ const condValue = this.evaluate(condition, context);
1756
+ return this.toBoolean(condValue) ? this.evaluate(ifTrue, context) : this.evaluate(ifFalse, context);
1757
+ }
1758
+ /**
1759
+ * Evaluate member access (object.property).
1760
+ * @private
1761
+ */
1762
+ evaluateMemberAccess(object, property, context) {
1763
+ const obj = this.evaluate(object, context);
1764
+ if (obj === null || obj === void 0) {
1765
+ throw new ParseError(
1766
+ `Cannot access property ${property} of null/undefined`
1767
+ );
1768
+ }
1769
+ if (typeof obj === "object") {
1770
+ return obj[property];
1771
+ }
1772
+ throw new ParseError(`Cannot access property ${property} of non-object`);
1773
+ }
1774
+ /**
1775
+ * Evaluate index access (array[index]).
1776
+ * @private
1777
+ */
1778
+ evaluateIndexAccess(object, index, context) {
1779
+ const obj = this.evaluate(object, context);
1780
+ const idx = this.evaluate(index, context);
1781
+ if (Array.isArray(obj)) {
1782
+ const numIdx = this.toInt(idx);
1783
+ return obj[numIdx];
1784
+ }
1785
+ if (obj instanceof Uint8Array) {
1786
+ const numIdx = this.toInt(idx);
1787
+ return obj[numIdx];
1788
+ }
1789
+ throw new ParseError("Index access requires an array");
1790
+ }
1791
+ /**
1792
+ * Evaluate method call (object.method()).
1793
+ * @private
1794
+ */
1795
+ evaluateMethodCall(object, method, _args, context) {
1796
+ const obj = this.evaluate(object, context);
1797
+ if (method === "length" || method === "size") {
1798
+ if (Array.isArray(obj)) return obj.length;
1799
+ if (obj instanceof Uint8Array) return obj.length;
1800
+ if (typeof obj === "string") return obj.length;
1801
+ throw new ParseError(`Object does not have a ${method} property`);
1802
+ }
1803
+ if (method === "to_i") {
1804
+ return this.toInt(obj);
1805
+ }
1806
+ if (method === "to_s") {
1807
+ return String(obj);
1808
+ }
1809
+ throw new ParseError(`Unknown method: ${method}`);
1810
+ }
1811
+ /**
1812
+ * Evaluate enum access (EnumName::value).
1813
+ * @private
1814
+ */
1815
+ evaluateEnumAccess(enumName, valueName, context) {
1816
+ const value = context.getEnumValue(enumName, valueName);
1817
+ if (value === void 0) {
1818
+ throw new ParseError(
1819
+ `Enum value "${enumName}::${valueName}" not found`
1820
+ );
1821
+ }
1822
+ return value;
1823
+ }
1824
+ /**
1825
+ * Helper: Add two values (handles strings and numbers).
1826
+ * @private
1827
+ */
1828
+ add(left, right) {
1829
+ if (typeof left === "string" || typeof right === "string") {
1830
+ return String(left) + String(right);
1831
+ }
1832
+ return this.toNumber(left) + this.toNumber(right);
1833
+ }
1834
+ /**
1835
+ * Helper: Modulo operation (Kaitai-style, not remainder).
1836
+ * @private
1837
+ */
1838
+ modulo(a, b) {
1839
+ const result = a % b;
1840
+ return result < 0 ? result + b : result;
1841
+ }
1842
+ /**
1843
+ * Helper: Compare two values.
1844
+ * @private
1845
+ */
1846
+ compare(left, right) {
1847
+ if (typeof left === "string" && typeof right === "string") {
1848
+ return left < right ? -1 : left > right ? 1 : 0;
1849
+ }
1850
+ const leftNum = this.toNumber(left);
1851
+ const rightNum = this.toNumber(right);
1852
+ return leftNum < rightNum ? -1 : leftNum > rightNum ? 1 : 0;
1853
+ }
1854
+ /**
1855
+ * Helper: Check equality.
1856
+ * @private
1857
+ */
1858
+ equals(left, right) {
1859
+ if (typeof left === "bigint" || typeof right === "bigint") {
1860
+ return BigInt(left) === BigInt(right);
1861
+ }
1862
+ return left === right;
1863
+ }
1864
+ /**
1865
+ * Helper: Convert to number.
1866
+ * @private
1867
+ */
1868
+ toNumber(value) {
1869
+ if (typeof value === "number") return value;
1870
+ if (typeof value === "bigint") return Number(value);
1871
+ if (typeof value === "boolean") return value ? 1 : 0;
1872
+ if (typeof value === "string") return parseFloat(value);
1873
+ throw new ParseError(`Cannot convert ${typeof value} to number`);
1874
+ }
1875
+ /**
1876
+ * Helper: Convert to integer.
1877
+ * @private
1878
+ */
1879
+ toInt(value) {
1880
+ return Math.floor(this.toNumber(value));
1881
+ }
1882
+ /**
1883
+ * Helper: Convert to boolean.
1884
+ * @private
1885
+ */
1886
+ toBoolean(value) {
1887
+ if (typeof value === "boolean") return value;
1888
+ if (typeof value === "number") return value !== 0;
1889
+ if (typeof value === "bigint") return value !== 0n;
1890
+ if (typeof value === "string") return value.length > 0;
1891
+ if (value === null || value === void 0) return false;
1892
+ return true;
1893
+ }
1894
+ };
1895
+
1896
+ // src/expression/index.ts
1897
+ function evaluateExpression(expression, context) {
1898
+ const lexer = new Lexer(expression);
1899
+ const tokens = lexer.tokenize();
1900
+ const parser = new ExpressionParser(tokens);
1901
+ const ast = parser.parse();
1902
+ const evaluator = new Evaluator();
1903
+ return evaluator.evaluate(ast, context);
1904
+ }
1905
+
963
1906
  // src/interpreter/TypeInterpreter.ts
964
1907
  var TypeInterpreter = class _TypeInterpreter {
965
1908
  /**
@@ -987,7 +1930,7 @@ var TypeInterpreter = class _TypeInterpreter {
987
1930
  */
988
1931
  parse(stream, parent) {
989
1932
  const result = {};
990
- const context = new Context(stream, result, parent);
1933
+ const context = new Context(stream, result, parent, this.schema.enums);
991
1934
  context.current = result;
992
1935
  if (this.schema.seq) {
993
1936
  for (const attr of this.schema.seq) {
@@ -1010,11 +1953,20 @@ var TypeInterpreter = class _TypeInterpreter {
1010
1953
  parseAttribute(attr, context) {
1011
1954
  const stream = context.io;
1012
1955
  if (attr.if) {
1013
- throw new NotImplementedError("Conditional parsing (if)");
1956
+ const condition = this.evaluateValue(attr.if, context);
1957
+ if (!condition) {
1958
+ return void 0;
1959
+ }
1014
1960
  }
1015
1961
  if (attr.pos !== void 0) {
1016
- const pos = typeof attr.pos === "number" ? attr.pos : 0;
1017
- stream.seek(pos);
1962
+ const pos = this.evaluateValue(attr.pos, context);
1963
+ if (typeof pos === "number") {
1964
+ stream.seek(pos);
1965
+ } else if (typeof pos === "bigint") {
1966
+ stream.seek(Number(pos));
1967
+ } else {
1968
+ throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
1969
+ }
1018
1970
  }
1019
1971
  if (attr.io) {
1020
1972
  throw new NotImplementedError("Custom I/O streams");
@@ -1025,7 +1977,8 @@ var TypeInterpreter = class _TypeInterpreter {
1025
1977
  if (attr.contents) {
1026
1978
  return this.parseContents(attr, context);
1027
1979
  }
1028
- return this.parseValue(attr, context);
1980
+ const value = this.parseValue(attr, context);
1981
+ return value;
1029
1982
  }
1030
1983
  /**
1031
1984
  * Parse a repeated attribute.
@@ -1036,14 +1989,22 @@ var TypeInterpreter = class _TypeInterpreter {
1036
1989
  * @private
1037
1990
  */
1038
1991
  parseRepeated(attr, context) {
1039
- const result = [];
1040
1992
  const stream = context.io;
1993
+ const result = [];
1041
1994
  switch (attr.repeat) {
1042
1995
  case "expr": {
1043
- const count = typeof attr["repeat-expr"] === "number" ? attr["repeat-expr"] : 0;
1996
+ const countValue = this.evaluateValue(attr["repeat-expr"], context);
1997
+ const count = typeof countValue === "number" ? countValue : typeof countValue === "bigint" ? Number(countValue) : 0;
1998
+ if (count < 0) {
1999
+ throw new ParseError(`repeat-expr must be non-negative, got ${count}`);
2000
+ }
1044
2001
  for (let i = 0; i < count; i++) {
1045
2002
  context.set("_index", i);
1046
- result.push(this.parseValue(attr, context));
2003
+ const value = this.parseAttribute(
2004
+ { ...attr, repeat: void 0, "repeat-expr": void 0 },
2005
+ context
2006
+ );
2007
+ result.push(value);
1047
2008
  }
1048
2009
  break;
1049
2010
  }
@@ -1058,7 +2019,25 @@ var TypeInterpreter = class _TypeInterpreter {
1058
2019
  if (!attr["repeat-until"]) {
1059
2020
  throw new ParseError("repeat-until expression is required");
1060
2021
  }
1061
- throw new NotImplementedError("repeat-until");
2022
+ let index = 0;
2023
+ while (true) {
2024
+ context.set("_index", index);
2025
+ const value = this.parseAttribute(
2026
+ { ...attr, repeat: void 0, "repeat-until": void 0 },
2027
+ context
2028
+ );
2029
+ result.push(value);
2030
+ context.set("_", value);
2031
+ const condition = this.evaluateValue(attr["repeat-until"], context);
2032
+ if (condition) {
2033
+ break;
2034
+ }
2035
+ if (stream.isEof()) {
2036
+ break;
2037
+ }
2038
+ index++;
2039
+ }
2040
+ break;
1062
2041
  }
1063
2042
  default:
1064
2043
  throw new ParseError(`Unknown repeat type: ${attr.repeat}`);
@@ -1111,7 +2090,11 @@ var TypeInterpreter = class _TypeInterpreter {
1111
2090
  const stream = context.io;
1112
2091
  const type = attr.type;
1113
2092
  if (attr.size !== void 0) {
1114
- const size = typeof attr.size === "number" ? attr.size : 0;
2093
+ const sizeValue = this.evaluateValue(attr.size, context);
2094
+ const size = typeof sizeValue === "number" ? sizeValue : typeof sizeValue === "bigint" ? Number(sizeValue) : 0;
2095
+ if (size < 0) {
2096
+ throw new ParseError(`size must be non-negative, got ${size}`);
2097
+ }
1115
2098
  if (type === "str" || !type) {
1116
2099
  const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
1117
2100
  if (type === "str") {
@@ -1157,6 +2140,9 @@ var TypeInterpreter = class _TypeInterpreter {
1157
2140
  if (this.schema.types && type in this.schema.types) {
1158
2141
  const typeSchema = this.schema.types[type];
1159
2142
  const meta = this.schema.meta || this.parentMeta;
2143
+ if (this.schema.enums && !typeSchema.enums) {
2144
+ typeSchema.enums = this.schema.enums;
2145
+ }
1160
2146
  const interpreter = new _TypeInterpreter(typeSchema, meta);
1161
2147
  return interpreter.parse(stream, context.current);
1162
2148
  }
@@ -1238,6 +2224,34 @@ var TypeInterpreter = class _TypeInterpreter {
1238
2224
  throw new ParseError(`Unknown float type: ${type}`);
1239
2225
  }
1240
2226
  }
2227
+ /**
2228
+ * Evaluate an expression or return a literal value.
2229
+ * If the value is a string, it's treated as an expression.
2230
+ * If it's a number or boolean, it's returned as-is.
2231
+ *
2232
+ * @param value - Expression string or literal value
2233
+ * @param context - Execution context
2234
+ * @returns Evaluated result
2235
+ * @private
2236
+ */
2237
+ evaluateValue(value, context) {
2238
+ if (value === void 0) {
2239
+ return void 0;
2240
+ }
2241
+ if (typeof value === "number" || typeof value === "boolean") {
2242
+ return value;
2243
+ }
2244
+ if (typeof value === "string") {
2245
+ try {
2246
+ return evaluateExpression(value, context);
2247
+ } catch (error) {
2248
+ throw new ParseError(
2249
+ `Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
2250
+ );
2251
+ }
2252
+ }
2253
+ return value;
2254
+ }
1241
2255
  };
1242
2256
 
1243
2257
  // src/index.ts
@@ -1316,6 +2330,42 @@ export {
1316
2330
  * @author Fabiano Pinto
1317
2331
  * @license MIT
1318
2332
  */
2333
+ /**
2334
+ * @fileoverview Token types for Kaitai Struct expression language
2335
+ * @module expression/Token
2336
+ * @author Fabiano Pinto
2337
+ * @license MIT
2338
+ */
2339
+ /**
2340
+ * @fileoverview Lexer for Kaitai Struct expression language
2341
+ * @module expression/Lexer
2342
+ * @author Fabiano Pinto
2343
+ * @license MIT
2344
+ */
2345
+ /**
2346
+ * @fileoverview Abstract Syntax Tree nodes for expression language
2347
+ * @module expression/AST
2348
+ * @author Fabiano Pinto
2349
+ * @license MIT
2350
+ */
2351
+ /**
2352
+ * @fileoverview Parser for Kaitai Struct expression language
2353
+ * @module expression/Parser
2354
+ * @author Fabiano Pinto
2355
+ * @license MIT
2356
+ */
2357
+ /**
2358
+ * @fileoverview Evaluator for Kaitai Struct expression AST
2359
+ * @module expression/Evaluator
2360
+ * @author Fabiano Pinto
2361
+ * @license MIT
2362
+ */
2363
+ /**
2364
+ * @fileoverview Expression evaluation module
2365
+ * @module expression
2366
+ * @author Fabiano Pinto
2367
+ * @license MIT
2368
+ */
1319
2369
  /**
1320
2370
  * @fileoverview Type interpreter for executing Kaitai Struct schemas
1321
2371
  * @module interpreter/TypeInterpreter