@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/README.md +13 -7
- package/dist/index.d.mts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +1063 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1063 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -20
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
|
-
|
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 =
|
1059
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|