@k67/kaitai-struct-ts 0.2.0 → 0.5.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 +47 -24
- package/dist/index.d.mts +74 -2
- package/dist/index.d.ts +74 -2
- package/dist/index.js +1223 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1223 -22
- 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,917 @@ 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(`Enum value "${enumName}::${valueName}" not found`);
|
1861
|
+
}
|
1862
|
+
return value;
|
1863
|
+
}
|
1864
|
+
/**
|
1865
|
+
* Helper: Add two values (handles strings and numbers).
|
1866
|
+
* @private
|
1867
|
+
*/
|
1868
|
+
add(left, right) {
|
1869
|
+
if (typeof left === "string" || typeof right === "string") {
|
1870
|
+
return String(left) + String(right);
|
1871
|
+
}
|
1872
|
+
return this.toNumber(left) + this.toNumber(right);
|
1873
|
+
}
|
1874
|
+
/**
|
1875
|
+
* Helper: Modulo operation (Kaitai-style, not remainder).
|
1876
|
+
* @private
|
1877
|
+
*/
|
1878
|
+
modulo(a, b) {
|
1879
|
+
const result = a % b;
|
1880
|
+
return result < 0 ? result + b : result;
|
1881
|
+
}
|
1882
|
+
/**
|
1883
|
+
* Helper: Compare two values.
|
1884
|
+
* @private
|
1885
|
+
*/
|
1886
|
+
compare(left, right) {
|
1887
|
+
if (typeof left === "string" && typeof right === "string") {
|
1888
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
1889
|
+
}
|
1890
|
+
const leftNum = this.toNumber(left);
|
1891
|
+
const rightNum = this.toNumber(right);
|
1892
|
+
return leftNum < rightNum ? -1 : leftNum > rightNum ? 1 : 0;
|
1893
|
+
}
|
1894
|
+
/**
|
1895
|
+
* Helper: Check equality.
|
1896
|
+
* @private
|
1897
|
+
*/
|
1898
|
+
equals(left, right) {
|
1899
|
+
if (typeof left === "bigint" || typeof right === "bigint") {
|
1900
|
+
return BigInt(left) === BigInt(right);
|
1901
|
+
}
|
1902
|
+
return left === right;
|
1903
|
+
}
|
1904
|
+
/**
|
1905
|
+
* Helper: Convert to number.
|
1906
|
+
* @private
|
1907
|
+
*/
|
1908
|
+
toNumber(value) {
|
1909
|
+
if (typeof value === "number") return value;
|
1910
|
+
if (typeof value === "bigint") return Number(value);
|
1911
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
1912
|
+
if (typeof value === "string") return parseFloat(value);
|
1913
|
+
throw new ParseError(`Cannot convert ${typeof value} to number`);
|
1914
|
+
}
|
1915
|
+
/**
|
1916
|
+
* Helper: Convert to integer.
|
1917
|
+
* @private
|
1918
|
+
*/
|
1919
|
+
toInt(value) {
|
1920
|
+
return Math.floor(this.toNumber(value));
|
1921
|
+
}
|
1922
|
+
/**
|
1923
|
+
* Helper: Convert to boolean.
|
1924
|
+
* @private
|
1925
|
+
*/
|
1926
|
+
toBoolean(value) {
|
1927
|
+
if (typeof value === "boolean") return value;
|
1928
|
+
if (typeof value === "number") return value !== 0;
|
1929
|
+
if (typeof value === "bigint") return value !== 0n;
|
1930
|
+
if (typeof value === "string") return value.length > 0;
|
1931
|
+
if (value === null || value === void 0) return false;
|
1932
|
+
return true;
|
1933
|
+
}
|
1934
|
+
};
|
1935
|
+
|
1936
|
+
// src/expression/index.ts
|
1937
|
+
function evaluateExpression(expression, context) {
|
1938
|
+
const lexer = new Lexer(expression);
|
1939
|
+
const tokens = lexer.tokenize();
|
1940
|
+
const parser = new ExpressionParser(tokens);
|
1941
|
+
const ast = parser.parse();
|
1942
|
+
const evaluator = new Evaluator();
|
1943
|
+
return evaluator.evaluate(ast, context);
|
1944
|
+
}
|
1945
|
+
|
1005
1946
|
// src/interpreter/TypeInterpreter.ts
|
1006
1947
|
var TypeInterpreter = class _TypeInterpreter {
|
1007
1948
|
/**
|
@@ -1025,12 +1966,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1025
1966
|
*
|
1026
1967
|
* @param stream - Binary stream to parse
|
1027
1968
|
* @param parent - Parent object (for nested types)
|
1969
|
+
* @param typeArgs - Arguments for parametric types
|
1028
1970
|
* @returns Parsed object
|
1029
1971
|
*/
|
1030
|
-
parse(stream, parent) {
|
1972
|
+
parse(stream, parent, typeArgs) {
|
1031
1973
|
const result = {};
|
1032
|
-
const context = new Context(stream, result, parent);
|
1974
|
+
const context = new Context(stream, result, parent, this.schema.enums);
|
1033
1975
|
context.current = result;
|
1976
|
+
if (typeArgs && this.schema.params) {
|
1977
|
+
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
1978
|
+
const param = this.schema.params[i];
|
1979
|
+
const argValue = typeArgs[i];
|
1980
|
+
const evaluatedArg = typeof argValue === "string" ? this.evaluateValue(argValue, context) : argValue;
|
1981
|
+
context.set(param.id, evaluatedArg);
|
1982
|
+
}
|
1983
|
+
}
|
1034
1984
|
if (this.schema.seq) {
|
1035
1985
|
for (const attr of this.schema.seq) {
|
1036
1986
|
const value = this.parseAttribute(attr, context);
|
@@ -1039,8 +1989,83 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1039
1989
|
}
|
1040
1990
|
}
|
1041
1991
|
}
|
1992
|
+
if (this.schema.instances) {
|
1993
|
+
this.setupInstances(result, stream, context);
|
1994
|
+
}
|
1042
1995
|
return result;
|
1043
1996
|
}
|
1997
|
+
/**
|
1998
|
+
* Set up lazy-evaluated instance getters.
|
1999
|
+
* Instances are computed on first access and cached.
|
2000
|
+
*
|
2001
|
+
* @param result - Result object to add getters to
|
2002
|
+
* @param stream - Stream for parsing
|
2003
|
+
* @param context - Execution context
|
2004
|
+
* @private
|
2005
|
+
*/
|
2006
|
+
setupInstances(result, stream, context) {
|
2007
|
+
if (!this.schema.instances) return;
|
2008
|
+
for (const [name, instance] of Object.entries(this.schema.instances)) {
|
2009
|
+
let cached = void 0;
|
2010
|
+
let evaluated = false;
|
2011
|
+
Object.defineProperty(result, name, {
|
2012
|
+
get: () => {
|
2013
|
+
if (!evaluated) {
|
2014
|
+
cached = this.parseInstance(
|
2015
|
+
instance,
|
2016
|
+
stream,
|
2017
|
+
context
|
2018
|
+
);
|
2019
|
+
evaluated = true;
|
2020
|
+
}
|
2021
|
+
return cached;
|
2022
|
+
},
|
2023
|
+
enumerable: true,
|
2024
|
+
configurable: true
|
2025
|
+
});
|
2026
|
+
}
|
2027
|
+
}
|
2028
|
+
/**
|
2029
|
+
* Parse an instance (lazy-evaluated field).
|
2030
|
+
*
|
2031
|
+
* @param instance - Instance specification
|
2032
|
+
* @param stream - Stream to read from
|
2033
|
+
* @param context - Execution context
|
2034
|
+
* @returns Parsed or calculated value
|
2035
|
+
* @private
|
2036
|
+
*/
|
2037
|
+
parseInstance(instance, stream, context) {
|
2038
|
+
if ("value" in instance) {
|
2039
|
+
return this.evaluateValue(
|
2040
|
+
instance.value,
|
2041
|
+
context
|
2042
|
+
);
|
2043
|
+
}
|
2044
|
+
const savedPos = stream.pos;
|
2045
|
+
try {
|
2046
|
+
if (instance.pos !== void 0) {
|
2047
|
+
const pos = this.evaluateValue(
|
2048
|
+
instance.pos,
|
2049
|
+
context
|
2050
|
+
);
|
2051
|
+
if (typeof pos === "number") {
|
2052
|
+
stream.seek(pos);
|
2053
|
+
} else if (typeof pos === "bigint") {
|
2054
|
+
stream.seek(Number(pos));
|
2055
|
+
} else {
|
2056
|
+
throw new ParseError(
|
2057
|
+
`pos must evaluate to a number, got ${typeof pos}`
|
2058
|
+
);
|
2059
|
+
}
|
2060
|
+
}
|
2061
|
+
const value = this.parseAttribute(instance, context);
|
2062
|
+
return value;
|
2063
|
+
} finally {
|
2064
|
+
if (instance.pos !== void 0) {
|
2065
|
+
stream.seek(savedPos);
|
2066
|
+
}
|
2067
|
+
}
|
2068
|
+
}
|
1044
2069
|
/**
|
1045
2070
|
* Parse a single attribute according to its specification.
|
1046
2071
|
*
|
@@ -1052,11 +2077,20 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1052
2077
|
parseAttribute(attr, context) {
|
1053
2078
|
const stream = context.io;
|
1054
2079
|
if (attr.if) {
|
1055
|
-
|
2080
|
+
const condition = this.evaluateValue(attr.if, context);
|
2081
|
+
if (!condition) {
|
2082
|
+
return void 0;
|
2083
|
+
}
|
1056
2084
|
}
|
1057
2085
|
if (attr.pos !== void 0) {
|
1058
|
-
const pos =
|
1059
|
-
|
2086
|
+
const pos = this.evaluateValue(attr.pos, context);
|
2087
|
+
if (typeof pos === "number") {
|
2088
|
+
stream.seek(pos);
|
2089
|
+
} else if (typeof pos === "bigint") {
|
2090
|
+
stream.seek(Number(pos));
|
2091
|
+
} else {
|
2092
|
+
throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
|
2093
|
+
}
|
1060
2094
|
}
|
1061
2095
|
if (attr.io) {
|
1062
2096
|
throw new NotImplementedError("Custom I/O streams");
|
@@ -1067,7 +2101,8 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1067
2101
|
if (attr.contents) {
|
1068
2102
|
return this.parseContents(attr, context);
|
1069
2103
|
}
|
1070
|
-
|
2104
|
+
const value = this.parseValue(attr, context);
|
2105
|
+
return value;
|
1071
2106
|
}
|
1072
2107
|
/**
|
1073
2108
|
* Parse a repeated attribute.
|
@@ -1078,14 +2113,22 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1078
2113
|
* @private
|
1079
2114
|
*/
|
1080
2115
|
parseRepeated(attr, context) {
|
1081
|
-
const result = [];
|
1082
2116
|
const stream = context.io;
|
2117
|
+
const result = [];
|
1083
2118
|
switch (attr.repeat) {
|
1084
2119
|
case "expr": {
|
1085
|
-
const
|
2120
|
+
const countValue = this.evaluateValue(attr["repeat-expr"], context);
|
2121
|
+
const count = typeof countValue === "number" ? countValue : typeof countValue === "bigint" ? Number(countValue) : 0;
|
2122
|
+
if (count < 0) {
|
2123
|
+
throw new ParseError(`repeat-expr must be non-negative, got ${count}`);
|
2124
|
+
}
|
1086
2125
|
for (let i = 0; i < count; i++) {
|
1087
2126
|
context.set("_index", i);
|
1088
|
-
|
2127
|
+
const value = this.parseAttribute(
|
2128
|
+
{ ...attr, repeat: void 0, "repeat-expr": void 0 },
|
2129
|
+
context
|
2130
|
+
);
|
2131
|
+
result.push(value);
|
1089
2132
|
}
|
1090
2133
|
break;
|
1091
2134
|
}
|
@@ -1100,7 +2143,25 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1100
2143
|
if (!attr["repeat-until"]) {
|
1101
2144
|
throw new ParseError("repeat-until expression is required");
|
1102
2145
|
}
|
1103
|
-
|
2146
|
+
let index = 0;
|
2147
|
+
while (true) {
|
2148
|
+
context.set("_index", index);
|
2149
|
+
const value = this.parseAttribute(
|
2150
|
+
{ ...attr, repeat: void 0, "repeat-until": void 0 },
|
2151
|
+
context
|
2152
|
+
);
|
2153
|
+
result.push(value);
|
2154
|
+
context.set("_", value);
|
2155
|
+
const condition = this.evaluateValue(attr["repeat-until"], context);
|
2156
|
+
if (condition) {
|
2157
|
+
break;
|
2158
|
+
}
|
2159
|
+
if (stream.isEof()) {
|
2160
|
+
break;
|
2161
|
+
}
|
2162
|
+
index++;
|
2163
|
+
}
|
2164
|
+
break;
|
1104
2165
|
}
|
1105
2166
|
default:
|
1106
2167
|
throw new ParseError(`Unknown repeat type: ${attr.repeat}`);
|
@@ -1153,17 +2214,34 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1153
2214
|
const stream = context.io;
|
1154
2215
|
const type = attr.type;
|
1155
2216
|
if (attr.size !== void 0) {
|
1156
|
-
const
|
2217
|
+
const sizeValue = this.evaluateValue(attr.size, context);
|
2218
|
+
const size = typeof sizeValue === "number" ? sizeValue : typeof sizeValue === "bigint" ? Number(sizeValue) : 0;
|
2219
|
+
if (size < 0) {
|
2220
|
+
throw new ParseError(`size must be non-negative, got ${size}`);
|
2221
|
+
}
|
1157
2222
|
if (type === "str" || !type) {
|
1158
2223
|
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2224
|
+
let data;
|
1159
2225
|
if (type === "str") {
|
1160
|
-
|
2226
|
+
data = stream.readBytes(size);
|
2227
|
+
if (attr.process) {
|
2228
|
+
data = this.applyProcessing(data, attr.process);
|
2229
|
+
}
|
2230
|
+
return new TextDecoder(encoding).decode(data);
|
1161
2231
|
} else {
|
1162
|
-
|
2232
|
+
data = stream.readBytes(size);
|
2233
|
+
if (attr.process) {
|
2234
|
+
data = this.applyProcessing(data, attr.process);
|
2235
|
+
}
|
2236
|
+
return data;
|
1163
2237
|
}
|
1164
2238
|
} else {
|
1165
|
-
|
1166
|
-
|
2239
|
+
let data = stream.readBytes(size);
|
2240
|
+
if (attr.process) {
|
2241
|
+
data = this.applyProcessing(data, attr.process);
|
2242
|
+
}
|
2243
|
+
const substream = new KaitaiStream(data);
|
2244
|
+
return this.parseType(type, substream, context, attr["type-args"]);
|
1167
2245
|
}
|
1168
2246
|
}
|
1169
2247
|
if (attr["size-eos"]) {
|
@@ -1178,7 +2256,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1178
2256
|
if (!type) {
|
1179
2257
|
throw new ParseError("Attribute must have either type, size, or contents");
|
1180
2258
|
}
|
1181
|
-
return this.parseType(type, stream, context);
|
2259
|
+
return this.parseType(type, stream, context, attr["type-args"]);
|
1182
2260
|
}
|
1183
2261
|
/**
|
1184
2262
|
* Parse a value of a specific type.
|
@@ -1186,12 +2264,17 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1186
2264
|
* @param type - Type name or switch specification
|
1187
2265
|
* @param stream - Stream to read from
|
1188
2266
|
* @param context - Execution context
|
2267
|
+
* @param typeArgs - Arguments for parametric types
|
1189
2268
|
* @returns Parsed value
|
1190
2269
|
* @private
|
1191
2270
|
*/
|
1192
|
-
parseType(type, stream, context) {
|
2271
|
+
parseType(type, stream, context, typeArgs) {
|
1193
2272
|
if (typeof type === "object") {
|
1194
|
-
|
2273
|
+
return this.parseSwitchType(
|
2274
|
+
type,
|
2275
|
+
stream,
|
2276
|
+
context
|
2277
|
+
);
|
1195
2278
|
}
|
1196
2279
|
if (isBuiltinType(type)) {
|
1197
2280
|
return this.parseBuiltinType(type, stream, context);
|
@@ -1199,11 +2282,49 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1199
2282
|
if (this.schema.types && type in this.schema.types) {
|
1200
2283
|
const typeSchema = this.schema.types[type];
|
1201
2284
|
const meta = this.schema.meta || this.parentMeta;
|
2285
|
+
if (this.schema.enums && !typeSchema.enums) {
|
2286
|
+
typeSchema.enums = this.schema.enums;
|
2287
|
+
}
|
2288
|
+
if (this.schema.types && !typeSchema.types) {
|
2289
|
+
typeSchema.types = this.schema.types;
|
2290
|
+
}
|
1202
2291
|
const interpreter = new _TypeInterpreter(typeSchema, meta);
|
1203
|
-
return interpreter.parse(stream, context.current);
|
2292
|
+
return interpreter.parse(stream, context.current, typeArgs);
|
1204
2293
|
}
|
1205
2294
|
throw new ParseError(`Unknown type: ${type}`);
|
1206
2295
|
}
|
2296
|
+
/**
|
2297
|
+
* Parse a switch type (type selection based on expression).
|
2298
|
+
*
|
2299
|
+
* @param switchType - Switch type specification
|
2300
|
+
* @param stream - Stream to read from
|
2301
|
+
* @param context - Execution context
|
2302
|
+
* @returns Parsed value
|
2303
|
+
* @private
|
2304
|
+
*/
|
2305
|
+
parseSwitchType(switchType, stream, context) {
|
2306
|
+
const switchOn = switchType["switch-on"];
|
2307
|
+
const cases = switchType["cases"];
|
2308
|
+
const defaultType = switchType["default"];
|
2309
|
+
if (!switchOn || typeof switchOn !== "string") {
|
2310
|
+
throw new ParseError("switch-on expression is required for switch types");
|
2311
|
+
}
|
2312
|
+
if (!cases) {
|
2313
|
+
throw new ParseError("cases are required for switch types");
|
2314
|
+
}
|
2315
|
+
const switchValue = this.evaluateValue(switchOn, context);
|
2316
|
+
const switchKey = String(switchValue);
|
2317
|
+
let selectedType = cases[switchKey];
|
2318
|
+
if (selectedType === void 0 && defaultType) {
|
2319
|
+
selectedType = defaultType;
|
2320
|
+
}
|
2321
|
+
if (selectedType === void 0) {
|
2322
|
+
throw new ParseError(
|
2323
|
+
`No matching case for switch value "${switchKey}" and no default type specified`
|
2324
|
+
);
|
2325
|
+
}
|
2326
|
+
return this.parseType(selectedType, stream, context);
|
2327
|
+
}
|
1207
2328
|
/**
|
1208
2329
|
* Parse a built-in type.
|
1209
2330
|
*
|
@@ -1280,6 +2401,50 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1280
2401
|
throw new ParseError(`Unknown float type: ${type}`);
|
1281
2402
|
}
|
1282
2403
|
}
|
2404
|
+
/**
|
2405
|
+
* Apply processing transformation to data.
|
2406
|
+
* Supports basic transformations like zlib decompression.
|
2407
|
+
*
|
2408
|
+
* @param data - Data to process
|
2409
|
+
* @param process - Processing specification
|
2410
|
+
* @returns Processed data
|
2411
|
+
* @private
|
2412
|
+
*/
|
2413
|
+
applyProcessing(data, process) {
|
2414
|
+
const processType = typeof process === "string" ? process : process.algorithm;
|
2415
|
+
if (processType) {
|
2416
|
+
throw new NotImplementedError(
|
2417
|
+
`Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
|
2418
|
+
);
|
2419
|
+
}
|
2420
|
+
return data;
|
2421
|
+
}
|
2422
|
+
/**
|
2423
|
+
* Evaluate a value that can be an expression or literal.
|
2424
|
+
*
|
2425
|
+
* @param value - Value to evaluate (expression string, number, or boolean)
|
2426
|
+
* @param context - Execution context
|
2427
|
+
* @returns Evaluated result
|
2428
|
+
* @private
|
2429
|
+
*/
|
2430
|
+
evaluateValue(value, context) {
|
2431
|
+
if (value === void 0) {
|
2432
|
+
return void 0;
|
2433
|
+
}
|
2434
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
2435
|
+
return value;
|
2436
|
+
}
|
2437
|
+
if (typeof value === "string") {
|
2438
|
+
try {
|
2439
|
+
return evaluateExpression(value, context);
|
2440
|
+
} catch (error) {
|
2441
|
+
throw new ParseError(
|
2442
|
+
`Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
|
2443
|
+
);
|
2444
|
+
}
|
2445
|
+
}
|
2446
|
+
return value;
|
2447
|
+
}
|
1283
2448
|
};
|
1284
2449
|
|
1285
2450
|
// src/index.ts
|
@@ -1359,6 +2524,42 @@ function parse(ksyYaml, buffer, options = {}) {
|
|
1359
2524
|
* @author Fabiano Pinto
|
1360
2525
|
* @license MIT
|
1361
2526
|
*/
|
2527
|
+
/**
|
2528
|
+
* @fileoverview Token types for Kaitai Struct expression language
|
2529
|
+
* @module expression/Token
|
2530
|
+
* @author Fabiano Pinto
|
2531
|
+
* @license MIT
|
2532
|
+
*/
|
2533
|
+
/**
|
2534
|
+
* @fileoverview Lexer for Kaitai Struct expression language
|
2535
|
+
* @module expression/Lexer
|
2536
|
+
* @author Fabiano Pinto
|
2537
|
+
* @license MIT
|
2538
|
+
*/
|
2539
|
+
/**
|
2540
|
+
* @fileoverview Abstract Syntax Tree nodes for expression language
|
2541
|
+
* @module expression/AST
|
2542
|
+
* @author Fabiano Pinto
|
2543
|
+
* @license MIT
|
2544
|
+
*/
|
2545
|
+
/**
|
2546
|
+
* @fileoverview Parser for Kaitai Struct expression language
|
2547
|
+
* @module expression/Parser
|
2548
|
+
* @author Fabiano Pinto
|
2549
|
+
* @license MIT
|
2550
|
+
*/
|
2551
|
+
/**
|
2552
|
+
* @fileoverview Evaluator for Kaitai Struct expression AST
|
2553
|
+
* @module expression/Evaluator
|
2554
|
+
* @author Fabiano Pinto
|
2555
|
+
* @license MIT
|
2556
|
+
*/
|
2557
|
+
/**
|
2558
|
+
* @fileoverview Expression evaluation module
|
2559
|
+
* @module expression
|
2560
|
+
* @author Fabiano Pinto
|
2561
|
+
* @license MIT
|
2562
|
+
*/
|
1362
2563
|
/**
|
1363
2564
|
* @fileoverview Type interpreter for executing Kaitai Struct schemas
|
1364
2565
|
* @module interpreter/TypeInterpreter
|