@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.mjs
CHANGED
@@ -824,17 +824,23 @@ var Context = class _Context {
|
|
824
824
|
* @param _io - Binary stream being read
|
825
825
|
* @param _root - Root object of the parse tree
|
826
826
|
* @param _parent - Parent object (optional)
|
827
|
+
* @param enums - Enum definitions from schema (optional)
|
827
828
|
*/
|
828
|
-
constructor(_io, _root = null, _parent = null) {
|
829
|
+
constructor(_io, _root = null, _parent = null, enums) {
|
829
830
|
this._io = _io;
|
830
831
|
this._root = _root;
|
831
832
|
/** Stack of parent objects */
|
832
833
|
this.parentStack = [];
|
833
834
|
/** Current object being parsed */
|
834
835
|
this._current = {};
|
836
|
+
/** Enum definitions from schema */
|
837
|
+
this._enums = {};
|
835
838
|
if (_parent !== null) {
|
836
839
|
this.parentStack.push(_parent);
|
837
840
|
}
|
841
|
+
if (enums) {
|
842
|
+
this._enums = enums;
|
843
|
+
}
|
838
844
|
}
|
839
845
|
/**
|
840
846
|
* Get the current I/O stream.
|
@@ -931,6 +937,36 @@ var Context = class _Context {
|
|
931
937
|
set(name, value) {
|
932
938
|
this._current[name] = value;
|
933
939
|
}
|
940
|
+
/**
|
941
|
+
* Get enum value by name.
|
942
|
+
* Used for enum access in expressions (EnumName::value).
|
943
|
+
*
|
944
|
+
* @param enumName - Name of the enum
|
945
|
+
* @param valueName - Name of the enum value
|
946
|
+
* @returns Enum value (number) or undefined
|
947
|
+
*/
|
948
|
+
getEnumValue(enumName, valueName) {
|
949
|
+
const enumDef = this._enums[enumName];
|
950
|
+
if (!enumDef) {
|
951
|
+
return void 0;
|
952
|
+
}
|
953
|
+
for (const [key, value] of Object.entries(enumDef)) {
|
954
|
+
if (value === valueName) {
|
955
|
+
const numKey = Number(key);
|
956
|
+
return isNaN(numKey) ? key : numKey;
|
957
|
+
}
|
958
|
+
}
|
959
|
+
return void 0;
|
960
|
+
}
|
961
|
+
/**
|
962
|
+
* Check if an enum exists.
|
963
|
+
*
|
964
|
+
* @param enumName - Name of the enum
|
965
|
+
* @returns True if enum exists
|
966
|
+
*/
|
967
|
+
hasEnum(enumName) {
|
968
|
+
return enumName in this._enums;
|
969
|
+
}
|
934
970
|
/**
|
935
971
|
* Create a child context for nested parsing.
|
936
972
|
* The current object becomes the parent in the child context.
|
@@ -942,7 +978,8 @@ var Context = class _Context {
|
|
942
978
|
const childContext = new _Context(
|
943
979
|
stream || this._io,
|
944
980
|
this._root || this._current,
|
945
|
-
this._current
|
981
|
+
this._current,
|
982
|
+
this._enums
|
946
983
|
);
|
947
984
|
return childContext;
|
948
985
|
}
|
@@ -953,13 +990,917 @@ var Context = class _Context {
|
|
953
990
|
* @returns Cloned context
|
954
991
|
*/
|
955
992
|
clone() {
|
956
|
-
const cloned = new _Context(this._io, this._root, this.parent);
|
993
|
+
const cloned = new _Context(this._io, this._root, this.parent, this._enums);
|
957
994
|
cloned._current = { ...this._current };
|
958
995
|
cloned.parentStack = [...this.parentStack];
|
959
996
|
return cloned;
|
960
997
|
}
|
961
998
|
};
|
962
999
|
|
1000
|
+
// src/expression/Token.ts
|
1001
|
+
function createToken(type, value = null, position = 0) {
|
1002
|
+
return { type, value, position };
|
1003
|
+
}
|
1004
|
+
|
1005
|
+
// src/expression/Lexer.ts
|
1006
|
+
var Lexer = class {
|
1007
|
+
/**
|
1008
|
+
* Create a new lexer.
|
1009
|
+
*
|
1010
|
+
* @param input - Expression string to tokenize
|
1011
|
+
*/
|
1012
|
+
constructor(input) {
|
1013
|
+
this.position = 0;
|
1014
|
+
this.current = null;
|
1015
|
+
this.input = input;
|
1016
|
+
this.current = input.length > 0 ? input[0] : null;
|
1017
|
+
}
|
1018
|
+
/**
|
1019
|
+
* Tokenize the entire input string.
|
1020
|
+
*
|
1021
|
+
* @returns Array of tokens
|
1022
|
+
* @throws {ParseError} If invalid syntax is encountered
|
1023
|
+
*/
|
1024
|
+
tokenize() {
|
1025
|
+
const tokens = [];
|
1026
|
+
while (this.current !== null) {
|
1027
|
+
if (this.isWhitespace(this.current)) {
|
1028
|
+
this.skipWhitespace();
|
1029
|
+
continue;
|
1030
|
+
}
|
1031
|
+
if (this.isDigit(this.current)) {
|
1032
|
+
tokens.push(this.readNumber());
|
1033
|
+
continue;
|
1034
|
+
}
|
1035
|
+
if (this.isIdentifierStart(this.current)) {
|
1036
|
+
tokens.push(this.readIdentifierOrKeyword());
|
1037
|
+
continue;
|
1038
|
+
}
|
1039
|
+
if (this.current === '"' || this.current === "'") {
|
1040
|
+
tokens.push(this.readString());
|
1041
|
+
continue;
|
1042
|
+
}
|
1043
|
+
const token = this.readOperator();
|
1044
|
+
if (token) {
|
1045
|
+
tokens.push(token);
|
1046
|
+
continue;
|
1047
|
+
}
|
1048
|
+
throw new ParseError(
|
1049
|
+
`Unexpected character: ${this.current}`,
|
1050
|
+
this.position
|
1051
|
+
);
|
1052
|
+
}
|
1053
|
+
tokens.push(createToken("EOF" /* EOF */, null, this.position));
|
1054
|
+
return tokens;
|
1055
|
+
}
|
1056
|
+
/**
|
1057
|
+
* Advance to the next character.
|
1058
|
+
* @private
|
1059
|
+
*/
|
1060
|
+
advance() {
|
1061
|
+
this.position++;
|
1062
|
+
this.current = this.position < this.input.length ? this.input[this.position] : null;
|
1063
|
+
}
|
1064
|
+
/**
|
1065
|
+
* Peek at the next character without advancing.
|
1066
|
+
* @private
|
1067
|
+
*/
|
1068
|
+
peek(offset = 1) {
|
1069
|
+
const pos = this.position + offset;
|
1070
|
+
return pos < this.input.length ? this.input[pos] : null;
|
1071
|
+
}
|
1072
|
+
/**
|
1073
|
+
* Check if character is whitespace.
|
1074
|
+
* @private
|
1075
|
+
*/
|
1076
|
+
isWhitespace(char) {
|
1077
|
+
return /\s/.test(char);
|
1078
|
+
}
|
1079
|
+
/**
|
1080
|
+
* Check if character is a digit.
|
1081
|
+
* @private
|
1082
|
+
*/
|
1083
|
+
isDigit(char) {
|
1084
|
+
return /[0-9]/.test(char);
|
1085
|
+
}
|
1086
|
+
/**
|
1087
|
+
* Check if character can start an identifier.
|
1088
|
+
* @private
|
1089
|
+
*/
|
1090
|
+
isIdentifierStart(char) {
|
1091
|
+
return /[a-zA-Z_]/.test(char);
|
1092
|
+
}
|
1093
|
+
/**
|
1094
|
+
* Check if character can be part of an identifier.
|
1095
|
+
* @private
|
1096
|
+
*/
|
1097
|
+
isIdentifierPart(char) {
|
1098
|
+
return /[a-zA-Z0-9_]/.test(char);
|
1099
|
+
}
|
1100
|
+
/**
|
1101
|
+
* Skip whitespace characters.
|
1102
|
+
* @private
|
1103
|
+
*/
|
1104
|
+
skipWhitespace() {
|
1105
|
+
while (this.current !== null && this.isWhitespace(this.current)) {
|
1106
|
+
this.advance();
|
1107
|
+
}
|
1108
|
+
}
|
1109
|
+
/**
|
1110
|
+
* Read a number token.
|
1111
|
+
* @private
|
1112
|
+
*/
|
1113
|
+
readNumber() {
|
1114
|
+
const start = this.position;
|
1115
|
+
let value = "";
|
1116
|
+
if (this.current === "0" && this.peek() === "x") {
|
1117
|
+
value += this.current;
|
1118
|
+
this.advance();
|
1119
|
+
value += this.current;
|
1120
|
+
this.advance();
|
1121
|
+
while (this.current !== null && /[0-9a-fA-F]/.test(this.current)) {
|
1122
|
+
value += this.current;
|
1123
|
+
this.advance();
|
1124
|
+
}
|
1125
|
+
return createToken("NUMBER" /* NUMBER */, parseInt(value, 16), start);
|
1126
|
+
}
|
1127
|
+
while (this.current !== null && this.isDigit(this.current)) {
|
1128
|
+
value += this.current;
|
1129
|
+
this.advance();
|
1130
|
+
}
|
1131
|
+
if (this.current === "." && this.peek() && this.isDigit(this.peek())) {
|
1132
|
+
value += this.current;
|
1133
|
+
this.advance();
|
1134
|
+
while (this.current !== null && this.isDigit(this.current)) {
|
1135
|
+
value += this.current;
|
1136
|
+
this.advance();
|
1137
|
+
}
|
1138
|
+
return createToken("NUMBER" /* NUMBER */, parseFloat(value), start);
|
1139
|
+
}
|
1140
|
+
return createToken("NUMBER" /* NUMBER */, parseInt(value, 10), start);
|
1141
|
+
}
|
1142
|
+
/**
|
1143
|
+
* Read an identifier or keyword token.
|
1144
|
+
* @private
|
1145
|
+
*/
|
1146
|
+
readIdentifierOrKeyword() {
|
1147
|
+
const start = this.position;
|
1148
|
+
let value = "";
|
1149
|
+
while (this.current !== null && this.isIdentifierPart(this.current)) {
|
1150
|
+
value += this.current;
|
1151
|
+
this.advance();
|
1152
|
+
}
|
1153
|
+
switch (value) {
|
1154
|
+
case "true":
|
1155
|
+
return createToken("BOOLEAN" /* BOOLEAN */, true, start);
|
1156
|
+
case "false":
|
1157
|
+
return createToken("BOOLEAN" /* BOOLEAN */, false, start);
|
1158
|
+
case "and":
|
1159
|
+
return createToken("AND" /* AND */, value, start);
|
1160
|
+
case "or":
|
1161
|
+
return createToken("OR" /* OR */, value, start);
|
1162
|
+
case "not":
|
1163
|
+
return createToken("NOT" /* NOT */, value, start);
|
1164
|
+
default:
|
1165
|
+
return createToken("IDENTIFIER" /* IDENTIFIER */, value, start);
|
1166
|
+
}
|
1167
|
+
}
|
1168
|
+
/**
|
1169
|
+
* Read a string token.
|
1170
|
+
* @private
|
1171
|
+
*/
|
1172
|
+
readString() {
|
1173
|
+
const start = this.position;
|
1174
|
+
const quote = this.current;
|
1175
|
+
let value = "";
|
1176
|
+
this.advance();
|
1177
|
+
while (this.current !== null && this.current !== quote) {
|
1178
|
+
if (this.current === "\\") {
|
1179
|
+
this.advance();
|
1180
|
+
if (this.current === null) {
|
1181
|
+
throw new ParseError("Unterminated string", start);
|
1182
|
+
}
|
1183
|
+
const ch = this.current;
|
1184
|
+
if (ch === "n") {
|
1185
|
+
value += "\n";
|
1186
|
+
} else if (ch === "t") {
|
1187
|
+
value += " ";
|
1188
|
+
} else if (ch === "r") {
|
1189
|
+
value += "\r";
|
1190
|
+
} else if (ch === "\\") {
|
1191
|
+
value += "\\";
|
1192
|
+
} else if (ch === '"') {
|
1193
|
+
value += '"';
|
1194
|
+
} else if (ch === "'") {
|
1195
|
+
value += "'";
|
1196
|
+
} else {
|
1197
|
+
value += ch;
|
1198
|
+
}
|
1199
|
+
} else {
|
1200
|
+
value += this.current;
|
1201
|
+
}
|
1202
|
+
this.advance();
|
1203
|
+
}
|
1204
|
+
if (this.current === null) {
|
1205
|
+
throw new ParseError("Unterminated string", start);
|
1206
|
+
}
|
1207
|
+
this.advance();
|
1208
|
+
return createToken("STRING" /* STRING */, value, start);
|
1209
|
+
}
|
1210
|
+
/**
|
1211
|
+
* Read an operator or punctuation token.
|
1212
|
+
* @private
|
1213
|
+
*/
|
1214
|
+
readOperator() {
|
1215
|
+
const start = this.position;
|
1216
|
+
const char = this.current;
|
1217
|
+
switch (char) {
|
1218
|
+
case "+":
|
1219
|
+
this.advance();
|
1220
|
+
return createToken("PLUS" /* PLUS */, char, start);
|
1221
|
+
case "-":
|
1222
|
+
this.advance();
|
1223
|
+
return createToken("MINUS" /* MINUS */, char, start);
|
1224
|
+
case "*":
|
1225
|
+
this.advance();
|
1226
|
+
return createToken("STAR" /* STAR */, char, start);
|
1227
|
+
case "/":
|
1228
|
+
this.advance();
|
1229
|
+
return createToken("SLASH" /* SLASH */, char, start);
|
1230
|
+
case "%":
|
1231
|
+
this.advance();
|
1232
|
+
return createToken("PERCENT" /* PERCENT */, char, start);
|
1233
|
+
case "<":
|
1234
|
+
this.advance();
|
1235
|
+
if (this.current === "=") {
|
1236
|
+
this.advance();
|
1237
|
+
return createToken("LE" /* LE */, "<=", start);
|
1238
|
+
} else if (this.current === "<") {
|
1239
|
+
this.advance();
|
1240
|
+
return createToken("LSHIFT" /* LSHIFT */, "<<", start);
|
1241
|
+
}
|
1242
|
+
return createToken("LT" /* LT */, "<", start);
|
1243
|
+
case ">":
|
1244
|
+
this.advance();
|
1245
|
+
if (this.current === "=") {
|
1246
|
+
this.advance();
|
1247
|
+
return createToken("GE" /* GE */, ">=", start);
|
1248
|
+
} else if (this.current === ">") {
|
1249
|
+
this.advance();
|
1250
|
+
return createToken("RSHIFT" /* RSHIFT */, ">>", start);
|
1251
|
+
}
|
1252
|
+
return createToken("GT" /* GT */, ">", start);
|
1253
|
+
case "=":
|
1254
|
+
this.advance();
|
1255
|
+
if (this.current === "=") {
|
1256
|
+
this.advance();
|
1257
|
+
return createToken("EQ" /* EQ */, "==", start);
|
1258
|
+
}
|
1259
|
+
throw new ParseError("Expected == for equality", start);
|
1260
|
+
case "!":
|
1261
|
+
this.advance();
|
1262
|
+
if (this.current === "=") {
|
1263
|
+
this.advance();
|
1264
|
+
return createToken("NE" /* NE */, "!=", start);
|
1265
|
+
}
|
1266
|
+
throw new ParseError("Expected != for inequality", start);
|
1267
|
+
case "&":
|
1268
|
+
this.advance();
|
1269
|
+
return createToken("AMPERSAND" /* AMPERSAND */, char, start);
|
1270
|
+
case "|":
|
1271
|
+
this.advance();
|
1272
|
+
return createToken("PIPE" /* PIPE */, char, start);
|
1273
|
+
case "^":
|
1274
|
+
this.advance();
|
1275
|
+
return createToken("CARET" /* CARET */, char, start);
|
1276
|
+
case "?":
|
1277
|
+
this.advance();
|
1278
|
+
return createToken("QUESTION" /* QUESTION */, char, start);
|
1279
|
+
case ":":
|
1280
|
+
this.advance();
|
1281
|
+
if (this.current === ":") {
|
1282
|
+
this.advance();
|
1283
|
+
return createToken("DOUBLE_COLON" /* DOUBLE_COLON */, "::", start);
|
1284
|
+
}
|
1285
|
+
return createToken("COLON" /* COLON */, char, start);
|
1286
|
+
case "(":
|
1287
|
+
this.advance();
|
1288
|
+
return createToken("LPAREN" /* LPAREN */, char, start);
|
1289
|
+
case ")":
|
1290
|
+
this.advance();
|
1291
|
+
return createToken("RPAREN" /* RPAREN */, char, start);
|
1292
|
+
case "[":
|
1293
|
+
this.advance();
|
1294
|
+
return createToken("LBRACKET" /* LBRACKET */, char, start);
|
1295
|
+
case "]":
|
1296
|
+
this.advance();
|
1297
|
+
return createToken("RBRACKET" /* RBRACKET */, char, start);
|
1298
|
+
case ".":
|
1299
|
+
this.advance();
|
1300
|
+
return createToken("DOT" /* DOT */, char, start);
|
1301
|
+
case ",":
|
1302
|
+
this.advance();
|
1303
|
+
return createToken("COMMA" /* COMMA */, char, start);
|
1304
|
+
default:
|
1305
|
+
return null;
|
1306
|
+
}
|
1307
|
+
}
|
1308
|
+
};
|
1309
|
+
|
1310
|
+
// src/expression/AST.ts
|
1311
|
+
function createLiteral(value) {
|
1312
|
+
return { kind: "Literal", value };
|
1313
|
+
}
|
1314
|
+
function createIdentifier(name) {
|
1315
|
+
return { kind: "Identifier", name };
|
1316
|
+
}
|
1317
|
+
function createBinaryOp(operator, left, right) {
|
1318
|
+
return { kind: "BinaryOp", operator, left, right };
|
1319
|
+
}
|
1320
|
+
function createUnaryOp(operator, operand) {
|
1321
|
+
return { kind: "UnaryOp", operator, operand };
|
1322
|
+
}
|
1323
|
+
function createTernary(condition, ifTrue, ifFalse) {
|
1324
|
+
return { kind: "Ternary", condition, ifTrue, ifFalse };
|
1325
|
+
}
|
1326
|
+
function createMemberAccess(object, property) {
|
1327
|
+
return { kind: "MemberAccess", object, property };
|
1328
|
+
}
|
1329
|
+
function createIndexAccess(object, index) {
|
1330
|
+
return { kind: "IndexAccess", object, index };
|
1331
|
+
}
|
1332
|
+
function createMethodCall(object, method, args) {
|
1333
|
+
return { kind: "MethodCall", object, method, args };
|
1334
|
+
}
|
1335
|
+
function createEnumAccess(enumName, value) {
|
1336
|
+
return { kind: "EnumAccess", enumName, value };
|
1337
|
+
}
|
1338
|
+
|
1339
|
+
// src/expression/Parser.ts
|
1340
|
+
var ExpressionParser = class {
|
1341
|
+
/**
|
1342
|
+
* Create a new expression parser.
|
1343
|
+
*
|
1344
|
+
* @param tokens - Array of tokens to parse
|
1345
|
+
*/
|
1346
|
+
constructor(tokens) {
|
1347
|
+
this.position = 0;
|
1348
|
+
this.tokens = tokens;
|
1349
|
+
}
|
1350
|
+
/**
|
1351
|
+
* Parse the tokens into an AST.
|
1352
|
+
*
|
1353
|
+
* @returns Root AST node
|
1354
|
+
* @throws {ParseError} If invalid syntax is encountered
|
1355
|
+
*/
|
1356
|
+
parse() {
|
1357
|
+
const expr = this.parseTernary();
|
1358
|
+
if (!this.isAtEnd()) {
|
1359
|
+
throw new ParseError(
|
1360
|
+
`Unexpected token: ${this.current().type}`,
|
1361
|
+
this.current().position
|
1362
|
+
);
|
1363
|
+
}
|
1364
|
+
return expr;
|
1365
|
+
}
|
1366
|
+
/**
|
1367
|
+
* Get the current token.
|
1368
|
+
* @private
|
1369
|
+
*/
|
1370
|
+
current() {
|
1371
|
+
return this.tokens[this.position];
|
1372
|
+
}
|
1373
|
+
/**
|
1374
|
+
* Check if we're at the end of tokens.
|
1375
|
+
* @private
|
1376
|
+
*/
|
1377
|
+
isAtEnd() {
|
1378
|
+
return this.current().type === "EOF" /* EOF */;
|
1379
|
+
}
|
1380
|
+
/**
|
1381
|
+
* Advance to the next token.
|
1382
|
+
* @private
|
1383
|
+
*/
|
1384
|
+
advance() {
|
1385
|
+
if (!this.isAtEnd()) {
|
1386
|
+
this.position++;
|
1387
|
+
}
|
1388
|
+
return this.tokens[this.position - 1];
|
1389
|
+
}
|
1390
|
+
/**
|
1391
|
+
* Check if current token matches any of the given types.
|
1392
|
+
* @private
|
1393
|
+
*/
|
1394
|
+
match(...types) {
|
1395
|
+
for (const type of types) {
|
1396
|
+
if (this.current().type === type) {
|
1397
|
+
this.advance();
|
1398
|
+
return true;
|
1399
|
+
}
|
1400
|
+
}
|
1401
|
+
return false;
|
1402
|
+
}
|
1403
|
+
/**
|
1404
|
+
* Expect a specific token type and advance.
|
1405
|
+
* @private
|
1406
|
+
*/
|
1407
|
+
expect(type, message) {
|
1408
|
+
if (this.current().type !== type) {
|
1409
|
+
throw new ParseError(message, this.current().position);
|
1410
|
+
}
|
1411
|
+
return this.advance();
|
1412
|
+
}
|
1413
|
+
/**
|
1414
|
+
* Parse ternary conditional (lowest precedence).
|
1415
|
+
* condition ? ifTrue : ifFalse
|
1416
|
+
* @private
|
1417
|
+
*/
|
1418
|
+
parseTernary() {
|
1419
|
+
let expr = this.parseLogicalOr();
|
1420
|
+
if (this.match("QUESTION" /* QUESTION */)) {
|
1421
|
+
const ifTrue = this.parseTernary();
|
1422
|
+
this.expect("COLON" /* COLON */, "Expected : in ternary expression");
|
1423
|
+
const ifFalse = this.parseTernary();
|
1424
|
+
return createTernary(expr, ifTrue, ifFalse);
|
1425
|
+
}
|
1426
|
+
return expr;
|
1427
|
+
}
|
1428
|
+
/**
|
1429
|
+
* Parse logical OR.
|
1430
|
+
* @private
|
1431
|
+
*/
|
1432
|
+
parseLogicalOr() {
|
1433
|
+
let left = this.parseLogicalAnd();
|
1434
|
+
while (this.match("OR" /* OR */)) {
|
1435
|
+
const operator = "or";
|
1436
|
+
const right = this.parseLogicalAnd();
|
1437
|
+
left = createBinaryOp(operator, left, right);
|
1438
|
+
}
|
1439
|
+
return left;
|
1440
|
+
}
|
1441
|
+
/**
|
1442
|
+
* Parse logical AND.
|
1443
|
+
* @private
|
1444
|
+
*/
|
1445
|
+
parseLogicalAnd() {
|
1446
|
+
let left = this.parseBitwiseOr();
|
1447
|
+
while (this.match("AND" /* AND */)) {
|
1448
|
+
const operator = "and";
|
1449
|
+
const right = this.parseBitwiseOr();
|
1450
|
+
left = createBinaryOp(operator, left, right);
|
1451
|
+
}
|
1452
|
+
return left;
|
1453
|
+
}
|
1454
|
+
/**
|
1455
|
+
* Parse bitwise OR.
|
1456
|
+
* @private
|
1457
|
+
*/
|
1458
|
+
parseBitwiseOr() {
|
1459
|
+
let left = this.parseBitwiseXor();
|
1460
|
+
while (this.match("PIPE" /* PIPE */)) {
|
1461
|
+
const operator = "|";
|
1462
|
+
const right = this.parseBitwiseXor();
|
1463
|
+
left = createBinaryOp(operator, left, right);
|
1464
|
+
}
|
1465
|
+
return left;
|
1466
|
+
}
|
1467
|
+
/**
|
1468
|
+
* Parse bitwise XOR.
|
1469
|
+
* @private
|
1470
|
+
*/
|
1471
|
+
parseBitwiseXor() {
|
1472
|
+
let left = this.parseBitwiseAnd();
|
1473
|
+
while (this.match("CARET" /* CARET */)) {
|
1474
|
+
const operator = "^";
|
1475
|
+
const right = this.parseBitwiseAnd();
|
1476
|
+
left = createBinaryOp(operator, left, right);
|
1477
|
+
}
|
1478
|
+
return left;
|
1479
|
+
}
|
1480
|
+
/**
|
1481
|
+
* Parse bitwise AND.
|
1482
|
+
* @private
|
1483
|
+
*/
|
1484
|
+
parseBitwiseAnd() {
|
1485
|
+
let left = this.parseEquality();
|
1486
|
+
while (this.match("AMPERSAND" /* AMPERSAND */)) {
|
1487
|
+
const operator = "&";
|
1488
|
+
const right = this.parseEquality();
|
1489
|
+
left = createBinaryOp(operator, left, right);
|
1490
|
+
}
|
1491
|
+
return left;
|
1492
|
+
}
|
1493
|
+
/**
|
1494
|
+
* Parse equality operators (==, !=).
|
1495
|
+
* @private
|
1496
|
+
*/
|
1497
|
+
parseEquality() {
|
1498
|
+
let left = this.parseRelational();
|
1499
|
+
while (this.match("EQ" /* EQ */, "NE" /* NE */)) {
|
1500
|
+
const operator = this.tokens[this.position - 1].value;
|
1501
|
+
const right = this.parseRelational();
|
1502
|
+
left = createBinaryOp(operator, left, right);
|
1503
|
+
}
|
1504
|
+
return left;
|
1505
|
+
}
|
1506
|
+
/**
|
1507
|
+
* Parse relational operators (<, <=, >, >=).
|
1508
|
+
* @private
|
1509
|
+
*/
|
1510
|
+
parseRelational() {
|
1511
|
+
let left = this.parseBitShift();
|
1512
|
+
while (this.match("LT" /* LT */, "LE" /* LE */, "GT" /* GT */, "GE" /* GE */)) {
|
1513
|
+
const operator = this.tokens[this.position - 1].value;
|
1514
|
+
const right = this.parseBitShift();
|
1515
|
+
left = createBinaryOp(operator, left, right);
|
1516
|
+
}
|
1517
|
+
return left;
|
1518
|
+
}
|
1519
|
+
/**
|
1520
|
+
* Parse bit shift operators (<<, >>).
|
1521
|
+
* @private
|
1522
|
+
*/
|
1523
|
+
parseBitShift() {
|
1524
|
+
let left = this.parseAdditive();
|
1525
|
+
while (this.match("LSHIFT" /* LSHIFT */, "RSHIFT" /* RSHIFT */)) {
|
1526
|
+
const operator = this.tokens[this.position - 1].value;
|
1527
|
+
const right = this.parseAdditive();
|
1528
|
+
left = createBinaryOp(operator, left, right);
|
1529
|
+
}
|
1530
|
+
return left;
|
1531
|
+
}
|
1532
|
+
/**
|
1533
|
+
* Parse additive operators (+, -).
|
1534
|
+
* @private
|
1535
|
+
*/
|
1536
|
+
parseAdditive() {
|
1537
|
+
let left = this.parseMultiplicative();
|
1538
|
+
while (this.match("PLUS" /* PLUS */, "MINUS" /* MINUS */)) {
|
1539
|
+
const operator = this.tokens[this.position - 1].value;
|
1540
|
+
const right = this.parseMultiplicative();
|
1541
|
+
left = createBinaryOp(operator, left, right);
|
1542
|
+
}
|
1543
|
+
return left;
|
1544
|
+
}
|
1545
|
+
/**
|
1546
|
+
* Parse multiplicative operators (*, /, %).
|
1547
|
+
* @private
|
1548
|
+
*/
|
1549
|
+
parseMultiplicative() {
|
1550
|
+
let left = this.parseUnary();
|
1551
|
+
while (this.match("STAR" /* STAR */, "SLASH" /* SLASH */, "PERCENT" /* PERCENT */)) {
|
1552
|
+
const operator = this.tokens[this.position - 1].value;
|
1553
|
+
const right = this.parseUnary();
|
1554
|
+
left = createBinaryOp(operator, left, right);
|
1555
|
+
}
|
1556
|
+
return left;
|
1557
|
+
}
|
1558
|
+
/**
|
1559
|
+
* Parse unary operators (-, not).
|
1560
|
+
* @private
|
1561
|
+
*/
|
1562
|
+
parseUnary() {
|
1563
|
+
if (this.match("MINUS" /* MINUS */, "NOT" /* NOT */)) {
|
1564
|
+
const operator = this.tokens[this.position - 1].value;
|
1565
|
+
const operand = this.parseUnary();
|
1566
|
+
return createUnaryOp(operator, operand);
|
1567
|
+
}
|
1568
|
+
return this.parsePostfix();
|
1569
|
+
}
|
1570
|
+
/**
|
1571
|
+
* Parse postfix operators (., [], ()).
|
1572
|
+
* @private
|
1573
|
+
*/
|
1574
|
+
parsePostfix() {
|
1575
|
+
let expr = this.parsePrimary();
|
1576
|
+
while (true) {
|
1577
|
+
if (this.match("DOT" /* DOT */)) {
|
1578
|
+
const property = this.expect(
|
1579
|
+
"IDENTIFIER" /* IDENTIFIER */,
|
1580
|
+
"Expected property name after ."
|
1581
|
+
);
|
1582
|
+
expr = createMemberAccess(expr, property.value);
|
1583
|
+
} else if (this.match("LBRACKET" /* LBRACKET */)) {
|
1584
|
+
const index = this.parseTernary();
|
1585
|
+
this.expect("RBRACKET" /* RBRACKET */, "Expected ] after array index");
|
1586
|
+
expr = createIndexAccess(expr, index);
|
1587
|
+
} else if (this.current().type === "LPAREN" /* LPAREN */) {
|
1588
|
+
if (expr.kind === "MemberAccess") {
|
1589
|
+
this.advance();
|
1590
|
+
const args = [];
|
1591
|
+
if (this.current().type !== "RPAREN" /* RPAREN */) {
|
1592
|
+
args.push(this.parseTernary());
|
1593
|
+
while (this.match("COMMA" /* COMMA */)) {
|
1594
|
+
args.push(this.parseTernary());
|
1595
|
+
}
|
1596
|
+
}
|
1597
|
+
this.expect("RPAREN" /* RPAREN */, "Expected ) after arguments");
|
1598
|
+
const memberExpr = expr;
|
1599
|
+
expr = createMethodCall(memberExpr.object, memberExpr.property, args);
|
1600
|
+
} else {
|
1601
|
+
break;
|
1602
|
+
}
|
1603
|
+
} else {
|
1604
|
+
break;
|
1605
|
+
}
|
1606
|
+
}
|
1607
|
+
return expr;
|
1608
|
+
}
|
1609
|
+
/**
|
1610
|
+
* Parse primary expressions (literals, identifiers, grouping).
|
1611
|
+
* @private
|
1612
|
+
*/
|
1613
|
+
parsePrimary() {
|
1614
|
+
if (this.match("NUMBER" /* NUMBER */, "STRING" /* STRING */, "BOOLEAN" /* BOOLEAN */)) {
|
1615
|
+
const token = this.tokens[this.position - 1];
|
1616
|
+
return createLiteral(token.value);
|
1617
|
+
}
|
1618
|
+
if (this.match("IDENTIFIER" /* IDENTIFIER */)) {
|
1619
|
+
const name = this.tokens[this.position - 1].value;
|
1620
|
+
if (this.match("DOUBLE_COLON" /* DOUBLE_COLON */)) {
|
1621
|
+
const value = this.expect(
|
1622
|
+
"IDENTIFIER" /* IDENTIFIER */,
|
1623
|
+
"Expected enum value after ::"
|
1624
|
+
);
|
1625
|
+
return createEnumAccess(name, value.value);
|
1626
|
+
}
|
1627
|
+
return createIdentifier(name);
|
1628
|
+
}
|
1629
|
+
if (this.match("LPAREN" /* LPAREN */)) {
|
1630
|
+
const expr = this.parseTernary();
|
1631
|
+
this.expect("RPAREN" /* RPAREN */, "Expected ) after expression");
|
1632
|
+
return expr;
|
1633
|
+
}
|
1634
|
+
throw new ParseError(
|
1635
|
+
`Unexpected token: ${this.current().type}`,
|
1636
|
+
this.current().position
|
1637
|
+
);
|
1638
|
+
}
|
1639
|
+
};
|
1640
|
+
|
1641
|
+
// src/expression/Evaluator.ts
|
1642
|
+
var Evaluator = class {
|
1643
|
+
/**
|
1644
|
+
* Evaluate an AST node in the given context.
|
1645
|
+
*
|
1646
|
+
* @param node - AST node to evaluate
|
1647
|
+
* @param context - Execution context
|
1648
|
+
* @returns Evaluated value
|
1649
|
+
* @throws {ParseError} If evaluation fails
|
1650
|
+
*/
|
1651
|
+
evaluate(node, context) {
|
1652
|
+
const n = node;
|
1653
|
+
switch (node.kind) {
|
1654
|
+
case "Literal":
|
1655
|
+
return n.value;
|
1656
|
+
case "Identifier":
|
1657
|
+
return this.evaluateIdentifier(n.name, context);
|
1658
|
+
case "BinaryOp":
|
1659
|
+
return this.evaluateBinaryOp(n.operator, n.left, n.right, context);
|
1660
|
+
case "UnaryOp":
|
1661
|
+
return this.evaluateUnaryOp(n.operator, n.operand, context);
|
1662
|
+
case "Ternary":
|
1663
|
+
return this.evaluateTernary(n.condition, n.ifTrue, n.ifFalse, context);
|
1664
|
+
case "MemberAccess":
|
1665
|
+
return this.evaluateMemberAccess(n.object, n.property, context);
|
1666
|
+
case "IndexAccess":
|
1667
|
+
return this.evaluateIndexAccess(n.object, n.index, context);
|
1668
|
+
case "MethodCall":
|
1669
|
+
return this.evaluateMethodCall(n.object, n.method, n.args, context);
|
1670
|
+
case "EnumAccess":
|
1671
|
+
return this.evaluateEnumAccess(n.enumName, n.value, context);
|
1672
|
+
default:
|
1673
|
+
throw new ParseError(`Unknown AST node kind: ${node.kind}`);
|
1674
|
+
}
|
1675
|
+
}
|
1676
|
+
/**
|
1677
|
+
* Evaluate an identifier.
|
1678
|
+
* @private
|
1679
|
+
*/
|
1680
|
+
evaluateIdentifier(name, context) {
|
1681
|
+
return context.resolve(name);
|
1682
|
+
}
|
1683
|
+
/**
|
1684
|
+
* Evaluate a binary operation.
|
1685
|
+
* @private
|
1686
|
+
*/
|
1687
|
+
evaluateBinaryOp(operator, left, right, context) {
|
1688
|
+
const leftVal = this.evaluate(left, context);
|
1689
|
+
const rightVal = this.evaluate(right, context);
|
1690
|
+
switch (operator) {
|
1691
|
+
// Arithmetic
|
1692
|
+
case "+":
|
1693
|
+
return this.add(leftVal, rightVal);
|
1694
|
+
case "-":
|
1695
|
+
return this.toNumber(leftVal) - this.toNumber(rightVal);
|
1696
|
+
case "*":
|
1697
|
+
return this.toNumber(leftVal) * this.toNumber(rightVal);
|
1698
|
+
case "/":
|
1699
|
+
return this.toNumber(leftVal) / this.toNumber(rightVal);
|
1700
|
+
case "%":
|
1701
|
+
return this.modulo(this.toNumber(leftVal), this.toNumber(rightVal));
|
1702
|
+
// Comparison
|
1703
|
+
case "<":
|
1704
|
+
return this.compare(leftVal, rightVal) < 0;
|
1705
|
+
case "<=":
|
1706
|
+
return this.compare(leftVal, rightVal) <= 0;
|
1707
|
+
case ">":
|
1708
|
+
return this.compare(leftVal, rightVal) > 0;
|
1709
|
+
case ">=":
|
1710
|
+
return this.compare(leftVal, rightVal) >= 0;
|
1711
|
+
case "==":
|
1712
|
+
return this.equals(leftVal, rightVal);
|
1713
|
+
case "!=":
|
1714
|
+
return !this.equals(leftVal, rightVal);
|
1715
|
+
// Bitwise
|
1716
|
+
case "<<":
|
1717
|
+
return this.toInt(leftVal) << this.toInt(rightVal);
|
1718
|
+
case ">>":
|
1719
|
+
return this.toInt(leftVal) >> this.toInt(rightVal);
|
1720
|
+
case "&":
|
1721
|
+
return this.toInt(leftVal) & this.toInt(rightVal);
|
1722
|
+
case "|":
|
1723
|
+
return this.toInt(leftVal) | this.toInt(rightVal);
|
1724
|
+
case "^":
|
1725
|
+
return this.toInt(leftVal) ^ this.toInt(rightVal);
|
1726
|
+
// Logical
|
1727
|
+
case "and":
|
1728
|
+
return this.toBoolean(leftVal) && this.toBoolean(rightVal);
|
1729
|
+
case "or":
|
1730
|
+
return this.toBoolean(leftVal) || this.toBoolean(rightVal);
|
1731
|
+
default:
|
1732
|
+
throw new ParseError(`Unknown binary operator: ${operator}`);
|
1733
|
+
}
|
1734
|
+
}
|
1735
|
+
/**
|
1736
|
+
* Evaluate a unary operation.
|
1737
|
+
* @private
|
1738
|
+
*/
|
1739
|
+
evaluateUnaryOp(operator, operand, context) {
|
1740
|
+
const value = this.evaluate(operand, context);
|
1741
|
+
switch (operator) {
|
1742
|
+
case "-":
|
1743
|
+
return -this.toNumber(value);
|
1744
|
+
case "not":
|
1745
|
+
return !this.toBoolean(value);
|
1746
|
+
default:
|
1747
|
+
throw new ParseError(`Unknown unary operator: ${operator}`);
|
1748
|
+
}
|
1749
|
+
}
|
1750
|
+
/**
|
1751
|
+
* Evaluate a ternary conditional.
|
1752
|
+
* @private
|
1753
|
+
*/
|
1754
|
+
evaluateTernary(condition, ifTrue, ifFalse, context) {
|
1755
|
+
const condValue = this.evaluate(condition, context);
|
1756
|
+
return this.toBoolean(condValue) ? this.evaluate(ifTrue, context) : this.evaluate(ifFalse, context);
|
1757
|
+
}
|
1758
|
+
/**
|
1759
|
+
* Evaluate member access (object.property).
|
1760
|
+
* @private
|
1761
|
+
*/
|
1762
|
+
evaluateMemberAccess(object, property, context) {
|
1763
|
+
const obj = this.evaluate(object, context);
|
1764
|
+
if (obj === null || obj === void 0) {
|
1765
|
+
throw new ParseError(
|
1766
|
+
`Cannot access property ${property} of null/undefined`
|
1767
|
+
);
|
1768
|
+
}
|
1769
|
+
if (typeof obj === "object") {
|
1770
|
+
return obj[property];
|
1771
|
+
}
|
1772
|
+
throw new ParseError(`Cannot access property ${property} of non-object`);
|
1773
|
+
}
|
1774
|
+
/**
|
1775
|
+
* Evaluate index access (array[index]).
|
1776
|
+
* @private
|
1777
|
+
*/
|
1778
|
+
evaluateIndexAccess(object, index, context) {
|
1779
|
+
const obj = this.evaluate(object, context);
|
1780
|
+
const idx = this.evaluate(index, context);
|
1781
|
+
if (Array.isArray(obj)) {
|
1782
|
+
const numIdx = this.toInt(idx);
|
1783
|
+
return obj[numIdx];
|
1784
|
+
}
|
1785
|
+
if (obj instanceof Uint8Array) {
|
1786
|
+
const numIdx = this.toInt(idx);
|
1787
|
+
return obj[numIdx];
|
1788
|
+
}
|
1789
|
+
throw new ParseError("Index access requires an array");
|
1790
|
+
}
|
1791
|
+
/**
|
1792
|
+
* Evaluate method call (object.method()).
|
1793
|
+
* @private
|
1794
|
+
*/
|
1795
|
+
evaluateMethodCall(object, method, _args, context) {
|
1796
|
+
const obj = this.evaluate(object, context);
|
1797
|
+
if (method === "length" || method === "size") {
|
1798
|
+
if (Array.isArray(obj)) return obj.length;
|
1799
|
+
if (obj instanceof Uint8Array) return obj.length;
|
1800
|
+
if (typeof obj === "string") return obj.length;
|
1801
|
+
throw new ParseError(`Object does not have a ${method} property`);
|
1802
|
+
}
|
1803
|
+
if (method === "to_i") {
|
1804
|
+
return this.toInt(obj);
|
1805
|
+
}
|
1806
|
+
if (method === "to_s") {
|
1807
|
+
return String(obj);
|
1808
|
+
}
|
1809
|
+
throw new ParseError(`Unknown method: ${method}`);
|
1810
|
+
}
|
1811
|
+
/**
|
1812
|
+
* Evaluate enum access (EnumName::value).
|
1813
|
+
* @private
|
1814
|
+
*/
|
1815
|
+
evaluateEnumAccess(enumName, valueName, context) {
|
1816
|
+
const value = context.getEnumValue(enumName, valueName);
|
1817
|
+
if (value === void 0) {
|
1818
|
+
throw new ParseError(`Enum value "${enumName}::${valueName}" not found`);
|
1819
|
+
}
|
1820
|
+
return value;
|
1821
|
+
}
|
1822
|
+
/**
|
1823
|
+
* Helper: Add two values (handles strings and numbers).
|
1824
|
+
* @private
|
1825
|
+
*/
|
1826
|
+
add(left, right) {
|
1827
|
+
if (typeof left === "string" || typeof right === "string") {
|
1828
|
+
return String(left) + String(right);
|
1829
|
+
}
|
1830
|
+
return this.toNumber(left) + this.toNumber(right);
|
1831
|
+
}
|
1832
|
+
/**
|
1833
|
+
* Helper: Modulo operation (Kaitai-style, not remainder).
|
1834
|
+
* @private
|
1835
|
+
*/
|
1836
|
+
modulo(a, b) {
|
1837
|
+
const result = a % b;
|
1838
|
+
return result < 0 ? result + b : result;
|
1839
|
+
}
|
1840
|
+
/**
|
1841
|
+
* Helper: Compare two values.
|
1842
|
+
* @private
|
1843
|
+
*/
|
1844
|
+
compare(left, right) {
|
1845
|
+
if (typeof left === "string" && typeof right === "string") {
|
1846
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
1847
|
+
}
|
1848
|
+
const leftNum = this.toNumber(left);
|
1849
|
+
const rightNum = this.toNumber(right);
|
1850
|
+
return leftNum < rightNum ? -1 : leftNum > rightNum ? 1 : 0;
|
1851
|
+
}
|
1852
|
+
/**
|
1853
|
+
* Helper: Check equality.
|
1854
|
+
* @private
|
1855
|
+
*/
|
1856
|
+
equals(left, right) {
|
1857
|
+
if (typeof left === "bigint" || typeof right === "bigint") {
|
1858
|
+
return BigInt(left) === BigInt(right);
|
1859
|
+
}
|
1860
|
+
return left === right;
|
1861
|
+
}
|
1862
|
+
/**
|
1863
|
+
* Helper: Convert to number.
|
1864
|
+
* @private
|
1865
|
+
*/
|
1866
|
+
toNumber(value) {
|
1867
|
+
if (typeof value === "number") return value;
|
1868
|
+
if (typeof value === "bigint") return Number(value);
|
1869
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
1870
|
+
if (typeof value === "string") return parseFloat(value);
|
1871
|
+
throw new ParseError(`Cannot convert ${typeof value} to number`);
|
1872
|
+
}
|
1873
|
+
/**
|
1874
|
+
* Helper: Convert to integer.
|
1875
|
+
* @private
|
1876
|
+
*/
|
1877
|
+
toInt(value) {
|
1878
|
+
return Math.floor(this.toNumber(value));
|
1879
|
+
}
|
1880
|
+
/**
|
1881
|
+
* Helper: Convert to boolean.
|
1882
|
+
* @private
|
1883
|
+
*/
|
1884
|
+
toBoolean(value) {
|
1885
|
+
if (typeof value === "boolean") return value;
|
1886
|
+
if (typeof value === "number") return value !== 0;
|
1887
|
+
if (typeof value === "bigint") return value !== 0n;
|
1888
|
+
if (typeof value === "string") return value.length > 0;
|
1889
|
+
if (value === null || value === void 0) return false;
|
1890
|
+
return true;
|
1891
|
+
}
|
1892
|
+
};
|
1893
|
+
|
1894
|
+
// src/expression/index.ts
|
1895
|
+
function evaluateExpression(expression, context) {
|
1896
|
+
const lexer = new Lexer(expression);
|
1897
|
+
const tokens = lexer.tokenize();
|
1898
|
+
const parser = new ExpressionParser(tokens);
|
1899
|
+
const ast = parser.parse();
|
1900
|
+
const evaluator = new Evaluator();
|
1901
|
+
return evaluator.evaluate(ast, context);
|
1902
|
+
}
|
1903
|
+
|
963
1904
|
// src/interpreter/TypeInterpreter.ts
|
964
1905
|
var TypeInterpreter = class _TypeInterpreter {
|
965
1906
|
/**
|
@@ -983,12 +1924,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
983
1924
|
*
|
984
1925
|
* @param stream - Binary stream to parse
|
985
1926
|
* @param parent - Parent object (for nested types)
|
1927
|
+
* @param typeArgs - Arguments for parametric types
|
986
1928
|
* @returns Parsed object
|
987
1929
|
*/
|
988
|
-
parse(stream, parent) {
|
1930
|
+
parse(stream, parent, typeArgs) {
|
989
1931
|
const result = {};
|
990
|
-
const context = new Context(stream, result, parent);
|
1932
|
+
const context = new Context(stream, result, parent, this.schema.enums);
|
991
1933
|
context.current = result;
|
1934
|
+
if (typeArgs && this.schema.params) {
|
1935
|
+
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
1936
|
+
const param = this.schema.params[i];
|
1937
|
+
const argValue = typeArgs[i];
|
1938
|
+
const evaluatedArg = typeof argValue === "string" ? this.evaluateValue(argValue, context) : argValue;
|
1939
|
+
context.set(param.id, evaluatedArg);
|
1940
|
+
}
|
1941
|
+
}
|
992
1942
|
if (this.schema.seq) {
|
993
1943
|
for (const attr of this.schema.seq) {
|
994
1944
|
const value = this.parseAttribute(attr, context);
|
@@ -997,8 +1947,83 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
997
1947
|
}
|
998
1948
|
}
|
999
1949
|
}
|
1950
|
+
if (this.schema.instances) {
|
1951
|
+
this.setupInstances(result, stream, context);
|
1952
|
+
}
|
1000
1953
|
return result;
|
1001
1954
|
}
|
1955
|
+
/**
|
1956
|
+
* Set up lazy-evaluated instance getters.
|
1957
|
+
* Instances are computed on first access and cached.
|
1958
|
+
*
|
1959
|
+
* @param result - Result object to add getters to
|
1960
|
+
* @param stream - Stream for parsing
|
1961
|
+
* @param context - Execution context
|
1962
|
+
* @private
|
1963
|
+
*/
|
1964
|
+
setupInstances(result, stream, context) {
|
1965
|
+
if (!this.schema.instances) return;
|
1966
|
+
for (const [name, instance] of Object.entries(this.schema.instances)) {
|
1967
|
+
let cached = void 0;
|
1968
|
+
let evaluated = false;
|
1969
|
+
Object.defineProperty(result, name, {
|
1970
|
+
get: () => {
|
1971
|
+
if (!evaluated) {
|
1972
|
+
cached = this.parseInstance(
|
1973
|
+
instance,
|
1974
|
+
stream,
|
1975
|
+
context
|
1976
|
+
);
|
1977
|
+
evaluated = true;
|
1978
|
+
}
|
1979
|
+
return cached;
|
1980
|
+
},
|
1981
|
+
enumerable: true,
|
1982
|
+
configurable: true
|
1983
|
+
});
|
1984
|
+
}
|
1985
|
+
}
|
1986
|
+
/**
|
1987
|
+
* Parse an instance (lazy-evaluated field).
|
1988
|
+
*
|
1989
|
+
* @param instance - Instance specification
|
1990
|
+
* @param stream - Stream to read from
|
1991
|
+
* @param context - Execution context
|
1992
|
+
* @returns Parsed or calculated value
|
1993
|
+
* @private
|
1994
|
+
*/
|
1995
|
+
parseInstance(instance, stream, context) {
|
1996
|
+
if ("value" in instance) {
|
1997
|
+
return this.evaluateValue(
|
1998
|
+
instance.value,
|
1999
|
+
context
|
2000
|
+
);
|
2001
|
+
}
|
2002
|
+
const savedPos = stream.pos;
|
2003
|
+
try {
|
2004
|
+
if (instance.pos !== void 0) {
|
2005
|
+
const pos = this.evaluateValue(
|
2006
|
+
instance.pos,
|
2007
|
+
context
|
2008
|
+
);
|
2009
|
+
if (typeof pos === "number") {
|
2010
|
+
stream.seek(pos);
|
2011
|
+
} else if (typeof pos === "bigint") {
|
2012
|
+
stream.seek(Number(pos));
|
2013
|
+
} else {
|
2014
|
+
throw new ParseError(
|
2015
|
+
`pos must evaluate to a number, got ${typeof pos}`
|
2016
|
+
);
|
2017
|
+
}
|
2018
|
+
}
|
2019
|
+
const value = this.parseAttribute(instance, context);
|
2020
|
+
return value;
|
2021
|
+
} finally {
|
2022
|
+
if (instance.pos !== void 0) {
|
2023
|
+
stream.seek(savedPos);
|
2024
|
+
}
|
2025
|
+
}
|
2026
|
+
}
|
1002
2027
|
/**
|
1003
2028
|
* Parse a single attribute according to its specification.
|
1004
2029
|
*
|
@@ -1010,11 +2035,20 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1010
2035
|
parseAttribute(attr, context) {
|
1011
2036
|
const stream = context.io;
|
1012
2037
|
if (attr.if) {
|
1013
|
-
|
2038
|
+
const condition = this.evaluateValue(attr.if, context);
|
2039
|
+
if (!condition) {
|
2040
|
+
return void 0;
|
2041
|
+
}
|
1014
2042
|
}
|
1015
2043
|
if (attr.pos !== void 0) {
|
1016
|
-
const pos =
|
1017
|
-
|
2044
|
+
const pos = this.evaluateValue(attr.pos, context);
|
2045
|
+
if (typeof pos === "number") {
|
2046
|
+
stream.seek(pos);
|
2047
|
+
} else if (typeof pos === "bigint") {
|
2048
|
+
stream.seek(Number(pos));
|
2049
|
+
} else {
|
2050
|
+
throw new ParseError(`pos must evaluate to a number, got ${typeof pos}`);
|
2051
|
+
}
|
1018
2052
|
}
|
1019
2053
|
if (attr.io) {
|
1020
2054
|
throw new NotImplementedError("Custom I/O streams");
|
@@ -1025,7 +2059,8 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1025
2059
|
if (attr.contents) {
|
1026
2060
|
return this.parseContents(attr, context);
|
1027
2061
|
}
|
1028
|
-
|
2062
|
+
const value = this.parseValue(attr, context);
|
2063
|
+
return value;
|
1029
2064
|
}
|
1030
2065
|
/**
|
1031
2066
|
* Parse a repeated attribute.
|
@@ -1036,14 +2071,22 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1036
2071
|
* @private
|
1037
2072
|
*/
|
1038
2073
|
parseRepeated(attr, context) {
|
1039
|
-
const result = [];
|
1040
2074
|
const stream = context.io;
|
2075
|
+
const result = [];
|
1041
2076
|
switch (attr.repeat) {
|
1042
2077
|
case "expr": {
|
1043
|
-
const
|
2078
|
+
const countValue = this.evaluateValue(attr["repeat-expr"], context);
|
2079
|
+
const count = typeof countValue === "number" ? countValue : typeof countValue === "bigint" ? Number(countValue) : 0;
|
2080
|
+
if (count < 0) {
|
2081
|
+
throw new ParseError(`repeat-expr must be non-negative, got ${count}`);
|
2082
|
+
}
|
1044
2083
|
for (let i = 0; i < count; i++) {
|
1045
2084
|
context.set("_index", i);
|
1046
|
-
|
2085
|
+
const value = this.parseAttribute(
|
2086
|
+
{ ...attr, repeat: void 0, "repeat-expr": void 0 },
|
2087
|
+
context
|
2088
|
+
);
|
2089
|
+
result.push(value);
|
1047
2090
|
}
|
1048
2091
|
break;
|
1049
2092
|
}
|
@@ -1058,7 +2101,25 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1058
2101
|
if (!attr["repeat-until"]) {
|
1059
2102
|
throw new ParseError("repeat-until expression is required");
|
1060
2103
|
}
|
1061
|
-
|
2104
|
+
let index = 0;
|
2105
|
+
while (true) {
|
2106
|
+
context.set("_index", index);
|
2107
|
+
const value = this.parseAttribute(
|
2108
|
+
{ ...attr, repeat: void 0, "repeat-until": void 0 },
|
2109
|
+
context
|
2110
|
+
);
|
2111
|
+
result.push(value);
|
2112
|
+
context.set("_", value);
|
2113
|
+
const condition = this.evaluateValue(attr["repeat-until"], context);
|
2114
|
+
if (condition) {
|
2115
|
+
break;
|
2116
|
+
}
|
2117
|
+
if (stream.isEof()) {
|
2118
|
+
break;
|
2119
|
+
}
|
2120
|
+
index++;
|
2121
|
+
}
|
2122
|
+
break;
|
1062
2123
|
}
|
1063
2124
|
default:
|
1064
2125
|
throw new ParseError(`Unknown repeat type: ${attr.repeat}`);
|
@@ -1111,17 +2172,34 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1111
2172
|
const stream = context.io;
|
1112
2173
|
const type = attr.type;
|
1113
2174
|
if (attr.size !== void 0) {
|
1114
|
-
const
|
2175
|
+
const sizeValue = this.evaluateValue(attr.size, context);
|
2176
|
+
const size = typeof sizeValue === "number" ? sizeValue : typeof sizeValue === "bigint" ? Number(sizeValue) : 0;
|
2177
|
+
if (size < 0) {
|
2178
|
+
throw new ParseError(`size must be non-negative, got ${size}`);
|
2179
|
+
}
|
1115
2180
|
if (type === "str" || !type) {
|
1116
2181
|
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2182
|
+
let data;
|
1117
2183
|
if (type === "str") {
|
1118
|
-
|
2184
|
+
data = stream.readBytes(size);
|
2185
|
+
if (attr.process) {
|
2186
|
+
data = this.applyProcessing(data, attr.process);
|
2187
|
+
}
|
2188
|
+
return new TextDecoder(encoding).decode(data);
|
1119
2189
|
} else {
|
1120
|
-
|
2190
|
+
data = stream.readBytes(size);
|
2191
|
+
if (attr.process) {
|
2192
|
+
data = this.applyProcessing(data, attr.process);
|
2193
|
+
}
|
2194
|
+
return data;
|
1121
2195
|
}
|
1122
2196
|
} else {
|
1123
|
-
|
1124
|
-
|
2197
|
+
let data = stream.readBytes(size);
|
2198
|
+
if (attr.process) {
|
2199
|
+
data = this.applyProcessing(data, attr.process);
|
2200
|
+
}
|
2201
|
+
const substream = new KaitaiStream(data);
|
2202
|
+
return this.parseType(type, substream, context, attr["type-args"]);
|
1125
2203
|
}
|
1126
2204
|
}
|
1127
2205
|
if (attr["size-eos"]) {
|
@@ -1136,7 +2214,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1136
2214
|
if (!type) {
|
1137
2215
|
throw new ParseError("Attribute must have either type, size, or contents");
|
1138
2216
|
}
|
1139
|
-
return this.parseType(type, stream, context);
|
2217
|
+
return this.parseType(type, stream, context, attr["type-args"]);
|
1140
2218
|
}
|
1141
2219
|
/**
|
1142
2220
|
* Parse a value of a specific type.
|
@@ -1144,12 +2222,17 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1144
2222
|
* @param type - Type name or switch specification
|
1145
2223
|
* @param stream - Stream to read from
|
1146
2224
|
* @param context - Execution context
|
2225
|
+
* @param typeArgs - Arguments for parametric types
|
1147
2226
|
* @returns Parsed value
|
1148
2227
|
* @private
|
1149
2228
|
*/
|
1150
|
-
parseType(type, stream, context) {
|
2229
|
+
parseType(type, stream, context, typeArgs) {
|
1151
2230
|
if (typeof type === "object") {
|
1152
|
-
|
2231
|
+
return this.parseSwitchType(
|
2232
|
+
type,
|
2233
|
+
stream,
|
2234
|
+
context
|
2235
|
+
);
|
1153
2236
|
}
|
1154
2237
|
if (isBuiltinType(type)) {
|
1155
2238
|
return this.parseBuiltinType(type, stream, context);
|
@@ -1157,11 +2240,49 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1157
2240
|
if (this.schema.types && type in this.schema.types) {
|
1158
2241
|
const typeSchema = this.schema.types[type];
|
1159
2242
|
const meta = this.schema.meta || this.parentMeta;
|
2243
|
+
if (this.schema.enums && !typeSchema.enums) {
|
2244
|
+
typeSchema.enums = this.schema.enums;
|
2245
|
+
}
|
2246
|
+
if (this.schema.types && !typeSchema.types) {
|
2247
|
+
typeSchema.types = this.schema.types;
|
2248
|
+
}
|
1160
2249
|
const interpreter = new _TypeInterpreter(typeSchema, meta);
|
1161
|
-
return interpreter.parse(stream, context.current);
|
2250
|
+
return interpreter.parse(stream, context.current, typeArgs);
|
1162
2251
|
}
|
1163
2252
|
throw new ParseError(`Unknown type: ${type}`);
|
1164
2253
|
}
|
2254
|
+
/**
|
2255
|
+
* Parse a switch type (type selection based on expression).
|
2256
|
+
*
|
2257
|
+
* @param switchType - Switch type specification
|
2258
|
+
* @param stream - Stream to read from
|
2259
|
+
* @param context - Execution context
|
2260
|
+
* @returns Parsed value
|
2261
|
+
* @private
|
2262
|
+
*/
|
2263
|
+
parseSwitchType(switchType, stream, context) {
|
2264
|
+
const switchOn = switchType["switch-on"];
|
2265
|
+
const cases = switchType["cases"];
|
2266
|
+
const defaultType = switchType["default"];
|
2267
|
+
if (!switchOn || typeof switchOn !== "string") {
|
2268
|
+
throw new ParseError("switch-on expression is required for switch types");
|
2269
|
+
}
|
2270
|
+
if (!cases) {
|
2271
|
+
throw new ParseError("cases are required for switch types");
|
2272
|
+
}
|
2273
|
+
const switchValue = this.evaluateValue(switchOn, context);
|
2274
|
+
const switchKey = String(switchValue);
|
2275
|
+
let selectedType = cases[switchKey];
|
2276
|
+
if (selectedType === void 0 && defaultType) {
|
2277
|
+
selectedType = defaultType;
|
2278
|
+
}
|
2279
|
+
if (selectedType === void 0) {
|
2280
|
+
throw new ParseError(
|
2281
|
+
`No matching case for switch value "${switchKey}" and no default type specified`
|
2282
|
+
);
|
2283
|
+
}
|
2284
|
+
return this.parseType(selectedType, stream, context);
|
2285
|
+
}
|
1165
2286
|
/**
|
1166
2287
|
* Parse a built-in type.
|
1167
2288
|
*
|
@@ -1238,6 +2359,50 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1238
2359
|
throw new ParseError(`Unknown float type: ${type}`);
|
1239
2360
|
}
|
1240
2361
|
}
|
2362
|
+
/**
|
2363
|
+
* Apply processing transformation to data.
|
2364
|
+
* Supports basic transformations like zlib decompression.
|
2365
|
+
*
|
2366
|
+
* @param data - Data to process
|
2367
|
+
* @param process - Processing specification
|
2368
|
+
* @returns Processed data
|
2369
|
+
* @private
|
2370
|
+
*/
|
2371
|
+
applyProcessing(data, process) {
|
2372
|
+
const processType = typeof process === "string" ? process : process.algorithm;
|
2373
|
+
if (processType) {
|
2374
|
+
throw new NotImplementedError(
|
2375
|
+
`Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
|
2376
|
+
);
|
2377
|
+
}
|
2378
|
+
return data;
|
2379
|
+
}
|
2380
|
+
/**
|
2381
|
+
* Evaluate a value that can be an expression or literal.
|
2382
|
+
*
|
2383
|
+
* @param value - Value to evaluate (expression string, number, or boolean)
|
2384
|
+
* @param context - Execution context
|
2385
|
+
* @returns Evaluated result
|
2386
|
+
* @private
|
2387
|
+
*/
|
2388
|
+
evaluateValue(value, context) {
|
2389
|
+
if (value === void 0) {
|
2390
|
+
return void 0;
|
2391
|
+
}
|
2392
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
2393
|
+
return value;
|
2394
|
+
}
|
2395
|
+
if (typeof value === "string") {
|
2396
|
+
try {
|
2397
|
+
return evaluateExpression(value, context);
|
2398
|
+
} catch (error) {
|
2399
|
+
throw new ParseError(
|
2400
|
+
`Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
|
2401
|
+
);
|
2402
|
+
}
|
2403
|
+
}
|
2404
|
+
return value;
|
2405
|
+
}
|
1241
2406
|
};
|
1242
2407
|
|
1243
2408
|
// src/index.ts
|
@@ -1316,6 +2481,42 @@ export {
|
|
1316
2481
|
* @author Fabiano Pinto
|
1317
2482
|
* @license MIT
|
1318
2483
|
*/
|
2484
|
+
/**
|
2485
|
+
* @fileoverview Token types for Kaitai Struct expression language
|
2486
|
+
* @module expression/Token
|
2487
|
+
* @author Fabiano Pinto
|
2488
|
+
* @license MIT
|
2489
|
+
*/
|
2490
|
+
/**
|
2491
|
+
* @fileoverview Lexer for Kaitai Struct expression language
|
2492
|
+
* @module expression/Lexer
|
2493
|
+
* @author Fabiano Pinto
|
2494
|
+
* @license MIT
|
2495
|
+
*/
|
2496
|
+
/**
|
2497
|
+
* @fileoverview Abstract Syntax Tree nodes for expression language
|
2498
|
+
* @module expression/AST
|
2499
|
+
* @author Fabiano Pinto
|
2500
|
+
* @license MIT
|
2501
|
+
*/
|
2502
|
+
/**
|
2503
|
+
* @fileoverview Parser for Kaitai Struct expression language
|
2504
|
+
* @module expression/Parser
|
2505
|
+
* @author Fabiano Pinto
|
2506
|
+
* @license MIT
|
2507
|
+
*/
|
2508
|
+
/**
|
2509
|
+
* @fileoverview Evaluator for Kaitai Struct expression AST
|
2510
|
+
* @module expression/Evaluator
|
2511
|
+
* @author Fabiano Pinto
|
2512
|
+
* @license MIT
|
2513
|
+
*/
|
2514
|
+
/**
|
2515
|
+
* @fileoverview Expression evaluation module
|
2516
|
+
* @module expression
|
2517
|
+
* @author Fabiano Pinto
|
2518
|
+
* @license MIT
|
2519
|
+
*/
|
1319
2520
|
/**
|
1320
2521
|
* @fileoverview Type interpreter for executing Kaitai Struct schemas
|
1321
2522
|
* @module interpreter/TypeInterpreter
|