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