@sdeverywhere/parse 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +714 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -1
- package/dist/index.d.ts +163 -1
- package/dist/index.js +704 -31
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -27,20 +27,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
29
|
// src/index.ts
|
|
30
|
-
var
|
|
31
|
-
__export(
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
canonicalFunctionId: () => canonicalFunctionId,
|
|
33
|
+
canonicalId: () => canonicalId,
|
|
34
|
+
canonicalVarId: () => canonicalVarId,
|
|
32
35
|
debugPrintExpr: () => debugPrintExpr,
|
|
33
36
|
parseVensimEquation: () => parseVensimEquation,
|
|
34
37
|
parseVensimExpr: () => parseVensimExpr,
|
|
35
38
|
parseVensimModel: () => parseVensimModel,
|
|
36
39
|
parseVensimSubscriptRange: () => parseVensimSubscriptRange,
|
|
40
|
+
parseXmileDimensionDef: () => parseXmileDimensionDef,
|
|
41
|
+
parseXmileModel: () => parseXmileModel,
|
|
42
|
+
parseXmileVariableDef: () => parseXmileVariableDef,
|
|
43
|
+
preprocessVensimModel: () => preprocessVensimModel,
|
|
37
44
|
prettyPrintExpr: () => prettyPrintExpr,
|
|
38
45
|
printExprStats: () => printExprStats,
|
|
39
46
|
reduceConditionals: () => reduceConditionals,
|
|
40
47
|
reduceExpr: () => reduceExpr,
|
|
41
48
|
toPrettyString: () => toPrettyString
|
|
42
49
|
});
|
|
43
|
-
module.exports = __toCommonJS(
|
|
50
|
+
module.exports = __toCommonJS(index_exports);
|
|
51
|
+
|
|
52
|
+
// src/_shared/canonical-id.js
|
|
53
|
+
var reTrailingMark = new RegExp("\\s+!$", "g");
|
|
54
|
+
var reWhitespace = new RegExp("(\\s|_)+", "g");
|
|
55
|
+
var reSpecialChars = new RegExp(`['"\\.,\\-\\$&%\\/\\|()]`, "g");
|
|
56
|
+
function canonicalId(name) {
|
|
57
|
+
return "_" + name.trim().replace(reTrailingMark, "!").replace(reWhitespace, "_").replace(reSpecialChars, "_").toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
function canonicalVarId(name) {
|
|
60
|
+
const m = name.match(/([^[]+)(?:\[([^\]]+)\])?/);
|
|
61
|
+
if (!m) {
|
|
62
|
+
throw new Error(`Invalid variable name: ${name}`);
|
|
63
|
+
}
|
|
64
|
+
let id = canonicalId(m[1]);
|
|
65
|
+
if (m[2]) {
|
|
66
|
+
const subscripts = m[2].split(",").map((x) => canonicalId(x));
|
|
67
|
+
id += `[${subscripts.join(",")}]`;
|
|
68
|
+
}
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
function canonicalFunctionId(name) {
|
|
72
|
+
return canonicalId(name).toUpperCase();
|
|
73
|
+
}
|
|
44
74
|
|
|
45
75
|
// src/ast/print-expr.ts
|
|
46
76
|
var import_assert_never = require("assert-never");
|
|
@@ -290,15 +320,13 @@ function fullIdForVarRef(varRef) {
|
|
|
290
320
|
// src/ast/reduce-expr.ts
|
|
291
321
|
var import_assert_never2 = require("assert-never");
|
|
292
322
|
|
|
293
|
-
// src/_shared/names.js
|
|
294
|
-
function canonicalName(name) {
|
|
295
|
-
return "_" + name.trim().replace(/"/g, "_").replace(/\s+!$/g, "!").replace(/\s/g, "_").replace(/,/g, "_").replace(/-/g, "_").replace(/\./g, "_").replace(/\$/g, "_").replace(/'/g, "_").replace(/&/g, "_").replace(/%/g, "_").replace(/\//g, "_").replace(/\|/g, "_").toLowerCase();
|
|
296
|
-
}
|
|
297
|
-
function cFunctionName(name) {
|
|
298
|
-
return canonicalName(name).toUpperCase();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
323
|
// src/ast/ast-builders.ts
|
|
324
|
+
function subRef(dimOrSubName) {
|
|
325
|
+
return {
|
|
326
|
+
subName: dimOrSubName,
|
|
327
|
+
subId: canonicalId(dimOrSubName)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
302
330
|
function num(value, text) {
|
|
303
331
|
return {
|
|
304
332
|
kind: "number",
|
|
@@ -327,6 +355,13 @@ function parens(expr) {
|
|
|
327
355
|
expr
|
|
328
356
|
};
|
|
329
357
|
}
|
|
358
|
+
function lookupDef(points, range) {
|
|
359
|
+
return {
|
|
360
|
+
kind: "lookup-def",
|
|
361
|
+
range,
|
|
362
|
+
points
|
|
363
|
+
};
|
|
364
|
+
}
|
|
330
365
|
function lookupCall(varRef, arg) {
|
|
331
366
|
return {
|
|
332
367
|
kind: "lookup-call",
|
|
@@ -334,6 +369,14 @@ function lookupCall(varRef, arg) {
|
|
|
334
369
|
arg
|
|
335
370
|
};
|
|
336
371
|
}
|
|
372
|
+
function call(fnName, ...args) {
|
|
373
|
+
return {
|
|
374
|
+
kind: "function-call",
|
|
375
|
+
fnName,
|
|
376
|
+
fnId: canonicalFunctionId(fnName),
|
|
377
|
+
args
|
|
378
|
+
};
|
|
379
|
+
}
|
|
337
380
|
|
|
338
381
|
// src/ast/reduce-expr.ts
|
|
339
382
|
function reduceExpr(expr, opts) {
|
|
@@ -673,7 +716,7 @@ var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
|
673
716
|
const ids = ctx.Id();
|
|
674
717
|
if (ids.length === 1) {
|
|
675
718
|
const dimName = ids[0].getText();
|
|
676
|
-
const dimId =
|
|
719
|
+
const dimId = canonicalId(dimName);
|
|
677
720
|
super.visitSubscriptRange(ctx);
|
|
678
721
|
return {
|
|
679
722
|
dimName,
|
|
@@ -683,7 +726,7 @@ var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
|
683
726
|
subscriptRefs: this.subscriptNames.map((subName) => {
|
|
684
727
|
return {
|
|
685
728
|
subName,
|
|
686
|
-
subId:
|
|
729
|
+
subId: canonicalId(subName)
|
|
687
730
|
};
|
|
688
731
|
}),
|
|
689
732
|
subscriptMappings: this.subscriptMappings,
|
|
@@ -691,9 +734,9 @@ var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
|
691
734
|
};
|
|
692
735
|
} else if (ids.length === 2) {
|
|
693
736
|
const dimName = ids[0].getText();
|
|
694
|
-
const dimId =
|
|
737
|
+
const dimId = canonicalId(dimName);
|
|
695
738
|
const familyName = ids[1].getText();
|
|
696
|
-
const familyId =
|
|
739
|
+
const familyId = canonicalId(familyName);
|
|
697
740
|
return {
|
|
698
741
|
dimName,
|
|
699
742
|
dimId,
|
|
@@ -733,11 +776,11 @@ var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
|
733
776
|
super.visitSubscriptMapping(ctx);
|
|
734
777
|
this.subscriptMappings.push({
|
|
735
778
|
toDimName,
|
|
736
|
-
toDimId:
|
|
779
|
+
toDimId: canonicalId(toDimName),
|
|
737
780
|
subscriptRefs: this.mappedSubscriptNames.map((subName) => {
|
|
738
781
|
return {
|
|
739
782
|
subName,
|
|
740
|
-
subId:
|
|
783
|
+
subId: canonicalId(subName)
|
|
741
784
|
};
|
|
742
785
|
})
|
|
743
786
|
});
|
|
@@ -747,7 +790,7 @@ var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
|
747
790
|
}
|
|
748
791
|
visitCall(ctx) {
|
|
749
792
|
const fnName = ctx.Id().getText();
|
|
750
|
-
const fnId =
|
|
793
|
+
const fnId = canonicalFunctionId(fnName);
|
|
751
794
|
if (fnId === "_GET_DIRECT_SUBSCRIPT") {
|
|
752
795
|
super.visitCall(ctx);
|
|
753
796
|
} else {
|
|
@@ -843,7 +886,7 @@ var ExprReader = class extends import_antlr4_vensim3.ModelVisitor {
|
|
|
843
886
|
//
|
|
844
887
|
visitCall(ctx) {
|
|
845
888
|
const vensimFnName = ctx.Id().getText();
|
|
846
|
-
const fnId =
|
|
889
|
+
const fnId = canonicalFunctionId(vensimFnName);
|
|
847
890
|
this.callStack.push({ fn: fnId, args: [] });
|
|
848
891
|
super.visitCall(ctx);
|
|
849
892
|
const callInfo = this.callStack.pop();
|
|
@@ -866,14 +909,14 @@ var ExprReader = class extends import_antlr4_vensim3.ModelVisitor {
|
|
|
866
909
|
}
|
|
867
910
|
visitVar(ctx) {
|
|
868
911
|
const vensimVarName = ctx.Id().getText().trim();
|
|
869
|
-
const varId =
|
|
912
|
+
const varId = canonicalId(vensimVarName);
|
|
870
913
|
this.subscripts = void 0;
|
|
871
914
|
super.visitVar(ctx);
|
|
872
915
|
const subscriptNames = this.subscripts;
|
|
873
916
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
874
917
|
return {
|
|
875
918
|
subName: name,
|
|
876
|
-
subId:
|
|
919
|
+
subId: canonicalId(name)
|
|
877
920
|
};
|
|
878
921
|
});
|
|
879
922
|
this.subscripts = void 0;
|
|
@@ -923,7 +966,7 @@ var ExprReader = class extends import_antlr4_vensim3.ModelVisitor {
|
|
|
923
966
|
}
|
|
924
967
|
visitLookupCall(ctx) {
|
|
925
968
|
const lookupVarName = ctx.Id().getText();
|
|
926
|
-
const lookupVarId =
|
|
969
|
+
const lookupVarId = canonicalId(lookupVarName);
|
|
927
970
|
if (ctx.subscriptList()) {
|
|
928
971
|
ctx.subscriptList().accept(this);
|
|
929
972
|
}
|
|
@@ -931,7 +974,7 @@ var ExprReader = class extends import_antlr4_vensim3.ModelVisitor {
|
|
|
931
974
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
932
975
|
return {
|
|
933
976
|
subName: name,
|
|
934
|
-
subId:
|
|
977
|
+
subId: canonicalId(name)
|
|
935
978
|
};
|
|
936
979
|
});
|
|
937
980
|
this.subscripts = void 0;
|
|
@@ -1128,13 +1171,13 @@ var EquationReader = class extends import_antlr4_vensim4.ModelVisitor {
|
|
|
1128
1171
|
}
|
|
1129
1172
|
visitLhs(ctx) {
|
|
1130
1173
|
const lhsVarName = ctx.Id().getText();
|
|
1131
|
-
const lhsVarId =
|
|
1174
|
+
const lhsVarId = canonicalId(lhsVarName);
|
|
1132
1175
|
super.visitLhs(ctx);
|
|
1133
1176
|
const subscriptNames = this.subscripts;
|
|
1134
1177
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
1135
1178
|
return {
|
|
1136
1179
|
subName: name,
|
|
1137
|
-
subId:
|
|
1180
|
+
subId: canonicalId(name)
|
|
1138
1181
|
};
|
|
1139
1182
|
});
|
|
1140
1183
|
const exceptSubscriptSets = this.exceptSubscriptSets;
|
|
@@ -1142,7 +1185,7 @@ var EquationReader = class extends import_antlr4_vensim4.ModelVisitor {
|
|
|
1142
1185
|
return subscriptSet.map((name) => {
|
|
1143
1186
|
return {
|
|
1144
1187
|
subName: name,
|
|
1145
|
-
subId:
|
|
1188
|
+
subId: canonicalId(name)
|
|
1146
1189
|
};
|
|
1147
1190
|
});
|
|
1148
1191
|
});
|
|
@@ -1222,16 +1265,41 @@ function parseVensimEquation(input) {
|
|
|
1222
1265
|
|
|
1223
1266
|
// src/vensim/preprocess-vensim.ts
|
|
1224
1267
|
var import_split_string = __toESM(require("split-string"), 1);
|
|
1225
|
-
function preprocessVensimModel(input) {
|
|
1268
|
+
function preprocessVensimModel(input, options) {
|
|
1269
|
+
const removalKeys = options?.removalKeys;
|
|
1270
|
+
function shouldRemove(text) {
|
|
1271
|
+
if (text.includes("TABBED ARRAY")) {
|
|
1272
|
+
return true;
|
|
1273
|
+
}
|
|
1274
|
+
if (removalKeys) {
|
|
1275
|
+
for (const key of removalKeys) {
|
|
1276
|
+
if (text.includes(key)) {
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
const macrosResult = removeMacros(input);
|
|
1284
|
+
input = macrosResult.processed;
|
|
1226
1285
|
const rawDefs = splitDefs(input);
|
|
1227
1286
|
const vensimDefs = [];
|
|
1287
|
+
const removedBlocks = [];
|
|
1228
1288
|
for (const rawDef of rawDefs) {
|
|
1289
|
+
if (shouldRemove(rawDef.text)) {
|
|
1290
|
+
removedBlocks.push(rawDef.text.trim() + "|");
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1229
1293
|
const vensimDef = processDef(rawDef);
|
|
1230
1294
|
if (vensimDef) {
|
|
1231
1295
|
vensimDefs.push(vensimDef);
|
|
1232
1296
|
}
|
|
1233
1297
|
}
|
|
1234
|
-
return
|
|
1298
|
+
return {
|
|
1299
|
+
defs: vensimDefs,
|
|
1300
|
+
removedMacros: macrosResult.removed,
|
|
1301
|
+
removedBlocks
|
|
1302
|
+
};
|
|
1235
1303
|
}
|
|
1236
1304
|
function splitDefs(input) {
|
|
1237
1305
|
const defTexts = (0, import_split_string.default)(input, { separator: "|", quotes: ['"'], keep: () => true });
|
|
@@ -1273,6 +1341,9 @@ function splitDefs(input) {
|
|
|
1273
1341
|
function splitLines(input) {
|
|
1274
1342
|
return input.split(/\r\n|\n|\r/);
|
|
1275
1343
|
}
|
|
1344
|
+
function splitExceptInQuoted(input, sep) {
|
|
1345
|
+
return (0, import_split_string.default)(input, { separator: sep, quotes: ['"'] });
|
|
1346
|
+
}
|
|
1276
1347
|
function processBackslashes(input) {
|
|
1277
1348
|
const inputLines = splitLines(input);
|
|
1278
1349
|
let output = "";
|
|
@@ -1318,22 +1389,27 @@ function replaceDelimitedStrings(str, open, close, newStr) {
|
|
|
1318
1389
|
function reduceWhitespace(input) {
|
|
1319
1390
|
return input.replace(/\s\s+/g, " ").trim();
|
|
1320
1391
|
}
|
|
1392
|
+
var reWhitespace2 = new RegExp("(\\s|_)+", "g");
|
|
1321
1393
|
function keyForDef(def) {
|
|
1322
1394
|
let key = def;
|
|
1323
1395
|
key = key.replace(/:INTERPOLATE:/g, "");
|
|
1396
|
+
let kind;
|
|
1324
1397
|
if (key.includes("=")) {
|
|
1398
|
+
kind = "eqn";
|
|
1325
1399
|
key = key.split("=")[0].trim();
|
|
1326
1400
|
} else if (key.includes(":")) {
|
|
1401
|
+
kind = "dim";
|
|
1327
1402
|
key = key.split(":")[0].trim();
|
|
1328
1403
|
} else {
|
|
1404
|
+
kind = "decl";
|
|
1329
1405
|
}
|
|
1406
|
+
key = splitExceptInQuoted(key, "(")[0];
|
|
1330
1407
|
key = key.replace(/"/g, "");
|
|
1331
|
-
key = key.split("(")[0];
|
|
1332
1408
|
key = key.trim();
|
|
1333
|
-
key = key.replace(/\
|
|
1334
|
-
key = key.replace(
|
|
1409
|
+
key = key.replace(/(?<=\[).*?(?=\])/g, (match) => match.replace(/\s/g, ""));
|
|
1410
|
+
key = key.replace(reWhitespace2, "_");
|
|
1335
1411
|
key = key.toLowerCase();
|
|
1336
|
-
return key;
|
|
1412
|
+
return { key, kind };
|
|
1337
1413
|
}
|
|
1338
1414
|
function processDef(rawDef) {
|
|
1339
1415
|
let input = rawDef.text;
|
|
@@ -1351,7 +1427,7 @@ function processDef(rawDef) {
|
|
|
1351
1427
|
${input}`);
|
|
1352
1428
|
}
|
|
1353
1429
|
const rawDefText = reduceWhitespace(parts[0]);
|
|
1354
|
-
const key = keyForDef(rawDefText);
|
|
1430
|
+
const { key, kind } = keyForDef(rawDefText);
|
|
1355
1431
|
const def = `${rawDefText} ~~|`;
|
|
1356
1432
|
const units = reduceWhitespace(parts[1]);
|
|
1357
1433
|
const comment = reduceWhitespace(parts[2]);
|
|
@@ -1359,12 +1435,25 @@ ${input}`);
|
|
|
1359
1435
|
return {
|
|
1360
1436
|
key,
|
|
1361
1437
|
def,
|
|
1438
|
+
kind,
|
|
1362
1439
|
line: rawDef.line,
|
|
1363
1440
|
units,
|
|
1364
1441
|
comment,
|
|
1365
1442
|
...group ? { group } : {}
|
|
1366
1443
|
};
|
|
1367
1444
|
}
|
|
1445
|
+
function removeMacros(input) {
|
|
1446
|
+
const removed = [];
|
|
1447
|
+
const processed = input.replace(/:MACRO:.*:END OF MACRO:/gms, (match) => {
|
|
1448
|
+
removed.push(match);
|
|
1449
|
+
const numBreaks = match.split(/\r\n|\n|\r/gms).length - 1;
|
|
1450
|
+
return numBreaks > 0 ? "\n".repeat(numBreaks) : "";
|
|
1451
|
+
});
|
|
1452
|
+
return {
|
|
1453
|
+
processed,
|
|
1454
|
+
removed
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1368
1457
|
|
|
1369
1458
|
// src/vensim/impl/model-reader.js
|
|
1370
1459
|
var import_antlr4_vensim5 = require("antlr4-vensim");
|
|
@@ -1423,7 +1512,7 @@ var ModelReader = class extends import_antlr4_vensim5.ModelVisitor {
|
|
|
1423
1512
|
function parseVensimModel(input, context, sort = false) {
|
|
1424
1513
|
const dimensions = [];
|
|
1425
1514
|
const equations = [];
|
|
1426
|
-
const defs = preprocessVensimModel(input);
|
|
1515
|
+
const { defs } = preprocessVensimModel(input);
|
|
1427
1516
|
if (sort) {
|
|
1428
1517
|
defs.sort((a, b) => {
|
|
1429
1518
|
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
@@ -1474,13 +1563,604 @@ Detail:
|
|
|
1474
1563
|
equations
|
|
1475
1564
|
};
|
|
1476
1565
|
}
|
|
1566
|
+
|
|
1567
|
+
// src/xmile/xml.ts
|
|
1568
|
+
var import_parse_xml = require("@rgrove/parse-xml");
|
|
1569
|
+
function firstElemOf(parent, tagName) {
|
|
1570
|
+
return parent?.children.find((n) => {
|
|
1571
|
+
if (n.type === import_parse_xml.XmlNode.TYPE_ELEMENT) {
|
|
1572
|
+
const e = n;
|
|
1573
|
+
return e.name === tagName;
|
|
1574
|
+
} else {
|
|
1575
|
+
return void 0;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
function firstTextOf(parent) {
|
|
1580
|
+
return parent?.children.find((n) => {
|
|
1581
|
+
return n.type === import_parse_xml.XmlNode.TYPE_TEXT;
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
function elemsOf(parent, tagNames) {
|
|
1585
|
+
if (parent === void 0) {
|
|
1586
|
+
return [];
|
|
1587
|
+
}
|
|
1588
|
+
const elems = [];
|
|
1589
|
+
for (const n of parent.children) {
|
|
1590
|
+
if (n.type === import_parse_xml.XmlNode.TYPE_ELEMENT) {
|
|
1591
|
+
const e = n;
|
|
1592
|
+
if (tagNames.includes(e.name)) {
|
|
1593
|
+
elems.push(e);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return elems;
|
|
1598
|
+
}
|
|
1599
|
+
function xmlError(elem, msg) {
|
|
1600
|
+
return `${msg}: ${JSON.stringify(elem.toJSON(), null, 2)}`;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/xmile/parse-xmile-dimension-def.ts
|
|
1604
|
+
function parseXmileDimensionDef(dimElem) {
|
|
1605
|
+
const dimName = dimElem.attributes?.name;
|
|
1606
|
+
if (dimName === void 0) {
|
|
1607
|
+
throw new Error(xmlError(dimElem, "<dim> name attribute is required for dimension definition"));
|
|
1608
|
+
}
|
|
1609
|
+
const elemElems = elemsOf(dimElem, ["elem"]);
|
|
1610
|
+
if (elemElems.length === 0) {
|
|
1611
|
+
throw new Error(xmlError(dimElem, "<dim> must contain one or more <elem> elements"));
|
|
1612
|
+
}
|
|
1613
|
+
const subscriptRefs = [];
|
|
1614
|
+
for (const elem of elemElems) {
|
|
1615
|
+
const subName = elem.attributes?.name;
|
|
1616
|
+
if (subName === void 0) {
|
|
1617
|
+
throw new Error(xmlError(dimElem, "<elem> name attribute is required for dimension element definition"));
|
|
1618
|
+
}
|
|
1619
|
+
const subId = canonicalId(subName);
|
|
1620
|
+
subscriptRefs.push({
|
|
1621
|
+
subId,
|
|
1622
|
+
subName
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
const comment = firstElemOf(dimElem, "doc")?.text || "";
|
|
1626
|
+
const dimId = canonicalId(dimName);
|
|
1627
|
+
return {
|
|
1628
|
+
dimName,
|
|
1629
|
+
dimId,
|
|
1630
|
+
// TODO: For Vensim `DimA <-> DimB` aliases, the family name would be `DimB`
|
|
1631
|
+
familyName: dimName,
|
|
1632
|
+
familyId: dimId,
|
|
1633
|
+
subscriptRefs,
|
|
1634
|
+
// TODO: Does XMILE support mappings?
|
|
1635
|
+
subscriptMappings: [],
|
|
1636
|
+
comment
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/xmile/parse-xmile-model.ts
|
|
1641
|
+
var import_parse_xml2 = require("@rgrove/parse-xml");
|
|
1642
|
+
|
|
1643
|
+
// src/xmile/parse-xmile-variable-def.ts
|
|
1644
|
+
function parseXmileVariableDef(varElem) {
|
|
1645
|
+
let varName = parseRequiredAttr(varElem, varElem, "name");
|
|
1646
|
+
varName = varName.replace(/\\n/g, " ");
|
|
1647
|
+
const varId = canonicalId(varName);
|
|
1648
|
+
const units = firstElemOf(varElem, "units")?.text || "";
|
|
1649
|
+
const comment = firstElemOf(varElem, "doc")?.text || "";
|
|
1650
|
+
function exprEquation(subscriptRefs, expr) {
|
|
1651
|
+
return {
|
|
1652
|
+
lhs: {
|
|
1653
|
+
varDef: {
|
|
1654
|
+
kind: "variable-def",
|
|
1655
|
+
varName,
|
|
1656
|
+
varId,
|
|
1657
|
+
subscriptRefs
|
|
1658
|
+
}
|
|
1659
|
+
},
|
|
1660
|
+
rhs: {
|
|
1661
|
+
kind: "expr",
|
|
1662
|
+
expr
|
|
1663
|
+
},
|
|
1664
|
+
units,
|
|
1665
|
+
comment
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function lookupEquation(subscriptRefs, lookup) {
|
|
1669
|
+
return {
|
|
1670
|
+
lhs: {
|
|
1671
|
+
varDef: {
|
|
1672
|
+
kind: "variable-def",
|
|
1673
|
+
varName,
|
|
1674
|
+
varId,
|
|
1675
|
+
subscriptRefs
|
|
1676
|
+
}
|
|
1677
|
+
},
|
|
1678
|
+
rhs: {
|
|
1679
|
+
kind: "lookup",
|
|
1680
|
+
lookupDef: lookup
|
|
1681
|
+
},
|
|
1682
|
+
units,
|
|
1683
|
+
comment
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
if (varElem.name === "gf") {
|
|
1687
|
+
const lookup = parseGfElem(varElem, varElem);
|
|
1688
|
+
return [lookupEquation(void 0, lookup)];
|
|
1689
|
+
}
|
|
1690
|
+
const dimensionsElem = firstElemOf(varElem, "dimensions");
|
|
1691
|
+
const equationDefs = [];
|
|
1692
|
+
if (dimensionsElem === void 0) {
|
|
1693
|
+
const gfElem = firstElemOf(varElem, "gf");
|
|
1694
|
+
if (gfElem) {
|
|
1695
|
+
if (varElem.name !== "flow" && varElem.name !== "aux") {
|
|
1696
|
+
throw new Error(xmlError(varElem, "<gf> is only allowed for <flow> and <aux> variables"));
|
|
1697
|
+
}
|
|
1698
|
+
const lookup = parseGfElem(varElem, gfElem);
|
|
1699
|
+
equationDefs.push(lookupEquation(void 0, lookup));
|
|
1700
|
+
} else {
|
|
1701
|
+
const expr = parseEqnElem(varElem, varElem);
|
|
1702
|
+
if (expr) {
|
|
1703
|
+
equationDefs.push(exprEquation(void 0, expr));
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
} else {
|
|
1707
|
+
const dimElems = elemsOf(dimensionsElem, ["dim"]);
|
|
1708
|
+
const dimNames = [];
|
|
1709
|
+
for (const dimElem of dimElems) {
|
|
1710
|
+
const dimName = dimElem.attributes?.name;
|
|
1711
|
+
if (dimName === void 0) {
|
|
1712
|
+
throw new Error(xmlError(varElem, "<dim> name attribute is required in <dimensions> for variable definition"));
|
|
1713
|
+
}
|
|
1714
|
+
dimNames.push(dimName);
|
|
1715
|
+
}
|
|
1716
|
+
const elementElems = elemsOf(varElem, ["element"]);
|
|
1717
|
+
if (elementElems.length === 0) {
|
|
1718
|
+
const dimRefs = dimNames.map(subRef);
|
|
1719
|
+
const expr = parseEqnElem(varElem, varElem);
|
|
1720
|
+
if (expr) {
|
|
1721
|
+
equationDefs.push(exprEquation(dimRefs, expr));
|
|
1722
|
+
}
|
|
1723
|
+
} else {
|
|
1724
|
+
for (const elementElem of elementElems) {
|
|
1725
|
+
const subscriptAttr = elementElem.attributes?.subscript;
|
|
1726
|
+
if (subscriptAttr === void 0) {
|
|
1727
|
+
throw new Error(xmlError(varElem, "<element> subscript attribute is required in variable definition"));
|
|
1728
|
+
}
|
|
1729
|
+
const subscriptNames = subscriptAttr.split(",").map((s) => s.trim());
|
|
1730
|
+
const subRefs = [];
|
|
1731
|
+
for (const subscriptName of subscriptNames) {
|
|
1732
|
+
if (!isNaN(parseInt(subscriptAttr))) {
|
|
1733
|
+
throw new Error(xmlError(varElem, "Numeric subscript indices are not currently supported"));
|
|
1734
|
+
}
|
|
1735
|
+
subRefs.push(subRef(subscriptName));
|
|
1736
|
+
}
|
|
1737
|
+
const expr = parseEqnElem(varElem, elementElem);
|
|
1738
|
+
if (expr) {
|
|
1739
|
+
equationDefs.push(exprEquation(subRefs, expr));
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return equationDefs;
|
|
1745
|
+
}
|
|
1746
|
+
function parseEqnElem(varElem, parentElem) {
|
|
1747
|
+
const varTagName = varElem.name;
|
|
1748
|
+
const eqnElem = firstElemOf(parentElem, "eqn");
|
|
1749
|
+
const eqnText = eqnElem ? firstTextOf(eqnElem) : void 0;
|
|
1750
|
+
switch (varTagName) {
|
|
1751
|
+
case "aux": {
|
|
1752
|
+
if (eqnText === void 0) {
|
|
1753
|
+
return void 0;
|
|
1754
|
+
}
|
|
1755
|
+
const initEqnElem = firstElemOf(parentElem, "init_eqn");
|
|
1756
|
+
const initEqnText = initEqnElem ? firstTextOf(initEqnElem) : void 0;
|
|
1757
|
+
if (initEqnText !== void 0) {
|
|
1758
|
+
const eqnExpr = parseExpr(eqnText.text);
|
|
1759
|
+
const initEqnExpr = parseExpr(initEqnText.text);
|
|
1760
|
+
return call("ACTIVE INITIAL", eqnExpr, initEqnExpr);
|
|
1761
|
+
}
|
|
1762
|
+
return parseExpr(eqnText.text);
|
|
1763
|
+
}
|
|
1764
|
+
case "stock": {
|
|
1765
|
+
if (eqnText === void 0) {
|
|
1766
|
+
throw new Error(xmlError(varElem, "An <eqn> is required for a <stock> variable"));
|
|
1767
|
+
}
|
|
1768
|
+
const inflowElems = elemsOf(parentElem, ["inflow"]);
|
|
1769
|
+
const outflowElems = elemsOf(parentElem, ["outflow"]);
|
|
1770
|
+
const inflowTexts = inflowElems.map((inflowElem) => {
|
|
1771
|
+
const inflowText = firstTextOf(inflowElem);
|
|
1772
|
+
if (inflowText === void 0) {
|
|
1773
|
+
throw new Error(xmlError(varElem, "An <inflow> must be non-empty for a <stock> variable"));
|
|
1774
|
+
}
|
|
1775
|
+
return inflowText.text;
|
|
1776
|
+
});
|
|
1777
|
+
const outflowTexts = outflowElems.map((outflowElem) => {
|
|
1778
|
+
const outflowText = firstTextOf(outflowElem);
|
|
1779
|
+
if (outflowText === void 0) {
|
|
1780
|
+
throw new Error(xmlError(varElem, "An <outflow> must be non-empty for a <stock> variable"));
|
|
1781
|
+
}
|
|
1782
|
+
return outflowText.text;
|
|
1783
|
+
});
|
|
1784
|
+
if (firstElemOf(parentElem, "conveyor")) {
|
|
1785
|
+
throw new Error(xmlError(varElem, "Currently <conveyor> is not supported for a <stock> variable"));
|
|
1786
|
+
}
|
|
1787
|
+
if (firstElemOf(parentElem, "queue")) {
|
|
1788
|
+
throw new Error(xmlError(varElem, "Currently <queue> is not supported for a <stock> variable"));
|
|
1789
|
+
}
|
|
1790
|
+
const inflowParts = inflowTexts.join(" + ");
|
|
1791
|
+
let outflowParts = outflowTexts.join(" - ");
|
|
1792
|
+
if (outflowTexts.length > 0) {
|
|
1793
|
+
if (inflowParts.length > 0) {
|
|
1794
|
+
outflowParts = `- ${outflowParts}`;
|
|
1795
|
+
} else {
|
|
1796
|
+
outflowParts = `-${outflowParts}`;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
const flowsExpr = parseExpr(`${inflowParts} ${outflowParts}`);
|
|
1800
|
+
const initExpr = parseExpr(eqnText.text);
|
|
1801
|
+
return call("INTEG", flowsExpr, initExpr);
|
|
1802
|
+
}
|
|
1803
|
+
case "flow":
|
|
1804
|
+
if (eqnText === void 0) {
|
|
1805
|
+
throw new Error(xmlError(varElem, "Currently <eqn> or <gf> is required for a <flow> variable"));
|
|
1806
|
+
}
|
|
1807
|
+
if (firstElemOf(parentElem, "multiplier")) {
|
|
1808
|
+
throw new Error(xmlError(varElem, "Currently <multiplier> is not supported for a <flow> variable"));
|
|
1809
|
+
}
|
|
1810
|
+
if (firstElemOf(parentElem, "overflow")) {
|
|
1811
|
+
throw new Error(xmlError(varElem, "Currently <overflow> is not supported for a <flow> variable"));
|
|
1812
|
+
}
|
|
1813
|
+
if (firstElemOf(parentElem, "leak")) {
|
|
1814
|
+
throw new Error(xmlError(varElem, "Currently <leak> is not supported for a <flow> variable"));
|
|
1815
|
+
}
|
|
1816
|
+
return parseExpr(eqnText.text);
|
|
1817
|
+
default:
|
|
1818
|
+
throw new Error(xmlError(varElem, `Unhandled variable type '${varTagName}'`));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function parseExpr(exprText) {
|
|
1822
|
+
exprText = convertConditionalExpressions(exprText);
|
|
1823
|
+
exprText = exprText.replace(/\[([^\]]*)\*([^\]]*)\]/g, "[$1_SDE_WILDCARD_!$2]");
|
|
1824
|
+
return parseVensimExpr(exprText);
|
|
1825
|
+
}
|
|
1826
|
+
function parseGfElem(varElem, gfElem) {
|
|
1827
|
+
const typeAttr = parseOptionalAttr(gfElem, "type");
|
|
1828
|
+
if (typeAttr && typeAttr !== "continuous") {
|
|
1829
|
+
throw new Error(xmlError(varElem, 'Currently "continuous" is the only type supported for <gf>'));
|
|
1830
|
+
}
|
|
1831
|
+
const yptsElem = firstElemOf(gfElem, "ypts");
|
|
1832
|
+
if (yptsElem === void 0) {
|
|
1833
|
+
throw new Error(xmlError(varElem, "<ypts> must be defined for a <gf>"));
|
|
1834
|
+
}
|
|
1835
|
+
const ypts = parseGfPts(varElem, yptsElem);
|
|
1836
|
+
if (ypts.length === 0) {
|
|
1837
|
+
throw new Error(xmlError(varElem, "<ypts> must have at least one element"));
|
|
1838
|
+
}
|
|
1839
|
+
const xptsElem = firstElemOf(gfElem, "xpts");
|
|
1840
|
+
const xscaleElem = firstElemOf(gfElem, "xscale");
|
|
1841
|
+
if (xptsElem && xscaleElem) {
|
|
1842
|
+
throw new Error(xmlError(varElem, "<gf> must contain <xpts> or <xscale> but not both"));
|
|
1843
|
+
} else if (xptsElem === void 0 && xscaleElem === void 0) {
|
|
1844
|
+
throw new Error(xmlError(varElem, "<gf> must contain either <xpts> or <xscale>"));
|
|
1845
|
+
}
|
|
1846
|
+
let xpts;
|
|
1847
|
+
if (xptsElem) {
|
|
1848
|
+
xpts = parseGfPts(varElem, xptsElem);
|
|
1849
|
+
if (xpts.length === 0) {
|
|
1850
|
+
throw new Error(xmlError(varElem, "<xpts> must have at least one element"));
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
const xMin = parseFloatAttr(varElem, xscaleElem, "min");
|
|
1854
|
+
const xMax = parseFloatAttr(varElem, xscaleElem, "max");
|
|
1855
|
+
if (xMin > xMax) {
|
|
1856
|
+
throw new Error(xmlError(varElem, "<xscale> max attribute must be > min attribute"));
|
|
1857
|
+
}
|
|
1858
|
+
xpts = Array(ypts.length);
|
|
1859
|
+
const xRange = xMax - xMin;
|
|
1860
|
+
if (ypts.length === 1) {
|
|
1861
|
+
xpts[0] = 0;
|
|
1862
|
+
} else {
|
|
1863
|
+
for (let i = 0; i < ypts.length; i++) {
|
|
1864
|
+
const frac = i / (ypts.length - 1);
|
|
1865
|
+
xpts[i] = xMin + xRange * frac;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (xpts.length !== ypts.length) {
|
|
1870
|
+
throw new Error(xmlError(varElem, "<xpts> and <ypts> must have the same number of elements"));
|
|
1871
|
+
}
|
|
1872
|
+
const points = [];
|
|
1873
|
+
for (let i = 0; i < xpts.length; i++) {
|
|
1874
|
+
points.push([xpts[i], ypts[i]]);
|
|
1875
|
+
}
|
|
1876
|
+
return lookupDef(points);
|
|
1877
|
+
}
|
|
1878
|
+
function parseGfPts(varElem, ptsElem) {
|
|
1879
|
+
const ptsText = firstTextOf(ptsElem)?.text;
|
|
1880
|
+
if (ptsText === void 0) {
|
|
1881
|
+
return [];
|
|
1882
|
+
}
|
|
1883
|
+
const sep = ptsElem.attributes?.sep || ",";
|
|
1884
|
+
const elems = ptsText.split(sep);
|
|
1885
|
+
const nums = [];
|
|
1886
|
+
for (const elem of elems) {
|
|
1887
|
+
const numText = elem.trim();
|
|
1888
|
+
const num2 = parseFloat(numText);
|
|
1889
|
+
if (isNaN(num2)) {
|
|
1890
|
+
console.log(JSON.stringify(ptsElem));
|
|
1891
|
+
throw new Error(xmlError(varElem, `Invalid number value '${numText}' in <${ptsElem.name}>'`));
|
|
1892
|
+
}
|
|
1893
|
+
nums.push(num2);
|
|
1894
|
+
}
|
|
1895
|
+
return nums;
|
|
1896
|
+
}
|
|
1897
|
+
function parseRequiredAttr(varElem, elem, attrName) {
|
|
1898
|
+
let s = elem.attributes && elem.attributes[attrName];
|
|
1899
|
+
s = s?.trim();
|
|
1900
|
+
if (s === void 0 || s.length === 0) {
|
|
1901
|
+
throw new Error(xmlError(varElem, `<${elem.name}> ${attrName} attribute is required`));
|
|
1902
|
+
}
|
|
1903
|
+
return s;
|
|
1904
|
+
}
|
|
1905
|
+
function parseOptionalAttr(elem, attrName) {
|
|
1906
|
+
const s = elem.attributes && elem.attributes[attrName];
|
|
1907
|
+
return s?.trim();
|
|
1908
|
+
}
|
|
1909
|
+
function parseFloatAttr(varElem, elem, attrName) {
|
|
1910
|
+
const s = parseRequiredAttr(varElem, elem, attrName);
|
|
1911
|
+
const num2 = parseFloat(s);
|
|
1912
|
+
if (isNaN(num2)) {
|
|
1913
|
+
throw new Error(xmlError(varElem, `Invalid number value '${s}' for <${elem.name}> ${attrName} attribute'`));
|
|
1914
|
+
}
|
|
1915
|
+
return num2;
|
|
1916
|
+
}
|
|
1917
|
+
function convertConditionalExpressions(exprText) {
|
|
1918
|
+
const normalizedText = exprText.trim().replace(/\s+/g, " ");
|
|
1919
|
+
const ifMatch = normalizedText.match(/\bIF\s+(.+)$/i);
|
|
1920
|
+
if (!ifMatch) {
|
|
1921
|
+
return exprText;
|
|
1922
|
+
}
|
|
1923
|
+
const ifIndex = normalizedText.search(/\bIF\s+/i);
|
|
1924
|
+
const beforeIf = normalizedText.substring(0, ifIndex);
|
|
1925
|
+
const afterIf = normalizedText.substring(ifIndex + 3).trim();
|
|
1926
|
+
const thenMatch = afterIf.match(/^(.+?)\s+THEN\s+(.+)$/i);
|
|
1927
|
+
if (!thenMatch) {
|
|
1928
|
+
return exprText;
|
|
1929
|
+
}
|
|
1930
|
+
const condition = thenMatch[1].trim();
|
|
1931
|
+
const afterThen = thenMatch[2];
|
|
1932
|
+
let elseIndex = -1;
|
|
1933
|
+
let parenCount = 0;
|
|
1934
|
+
let inQuotes = false;
|
|
1935
|
+
let quoteChar = "";
|
|
1936
|
+
for (let i = 0; i < afterThen.length; i++) {
|
|
1937
|
+
const char = afterThen[i];
|
|
1938
|
+
if ((char === '"' || char === "'") && (i === 0 || afterThen[i - 1] !== "\\")) {
|
|
1939
|
+
if (!inQuotes) {
|
|
1940
|
+
inQuotes = true;
|
|
1941
|
+
quoteChar = char;
|
|
1942
|
+
} else if (char === quoteChar) {
|
|
1943
|
+
inQuotes = false;
|
|
1944
|
+
quoteChar = "";
|
|
1945
|
+
}
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (inQuotes) {
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (char === "(") {
|
|
1952
|
+
parenCount++;
|
|
1953
|
+
} else if (char === ")") {
|
|
1954
|
+
parenCount--;
|
|
1955
|
+
}
|
|
1956
|
+
if (parenCount === 0 && !inQuotes) {
|
|
1957
|
+
const elseMatch = afterThen.substring(i).match(/^ELSE\s+(.+)$/i);
|
|
1958
|
+
if (elseMatch) {
|
|
1959
|
+
elseIndex = i;
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (elseIndex === -1) {
|
|
1965
|
+
return exprText;
|
|
1966
|
+
}
|
|
1967
|
+
const trueExpr = afterThen.substring(0, elseIndex).trim();
|
|
1968
|
+
let falseExpr = afterThen.substring(elseIndex + 5).trim();
|
|
1969
|
+
let endIndex = -1;
|
|
1970
|
+
parenCount = 0;
|
|
1971
|
+
inQuotes = false;
|
|
1972
|
+
quoteChar = "";
|
|
1973
|
+
for (let i = 0; i < falseExpr.length; i++) {
|
|
1974
|
+
const char = falseExpr[i];
|
|
1975
|
+
if ((char === '"' || char === "'") && (i === 0 || falseExpr[i - 1] !== "\\")) {
|
|
1976
|
+
if (!inQuotes) {
|
|
1977
|
+
inQuotes = true;
|
|
1978
|
+
quoteChar = char;
|
|
1979
|
+
} else if (char === quoteChar) {
|
|
1980
|
+
inQuotes = false;
|
|
1981
|
+
quoteChar = "";
|
|
1982
|
+
}
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
if (inQuotes) {
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
if (char === "(") {
|
|
1989
|
+
parenCount++;
|
|
1990
|
+
} else if (char === ")") {
|
|
1991
|
+
if (parenCount === 0) {
|
|
1992
|
+
endIndex = i;
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
parenCount--;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (endIndex !== -1) {
|
|
1999
|
+
falseExpr = falseExpr.substring(0, endIndex).trim();
|
|
2000
|
+
}
|
|
2001
|
+
const convertedTrueExpr = convertConditionalExpressions(trueExpr);
|
|
2002
|
+
const convertedFalseExpr = convertConditionalExpressions(falseExpr);
|
|
2003
|
+
const convertedCondition = condition.replace(/(?<!".*?)\b AND \b(?!.*?")/gi, " :AND: ").replace(/(?<!".*?)\b OR \b(?!.*?")/gi, " :OR: ").replace(/(?<!".*?)\b\s?NOT \b(?!.*?")/gi, " :NOT: ").replace(/^\((.+)\)$/, "$1");
|
|
2004
|
+
const elseStartInAfterIf = afterIf.indexOf(" ELSE ") + 6;
|
|
2005
|
+
const falseExprStartInAfterIf = elseStartInAfterIf;
|
|
2006
|
+
const falseExprEndInAfterIf = falseExprStartInAfterIf + falseExpr.length;
|
|
2007
|
+
const conditionalEndInNormalizedText = ifIndex + 3 + falseExprEndInAfterIf;
|
|
2008
|
+
const afterConditional = normalizedText.substring(conditionalEndInNormalizedText).trim();
|
|
2009
|
+
return `${beforeIf}IF THEN ELSE(${convertedCondition}, ${convertedTrueExpr}, ${convertedFalseExpr})${afterConditional}`;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// src/xmile/parse-xmile-model.ts
|
|
2013
|
+
function parseXmileModel(input) {
|
|
2014
|
+
let xml;
|
|
2015
|
+
try {
|
|
2016
|
+
xml = (0, import_parse_xml2.parseXml)(input, { includeOffsets: true });
|
|
2017
|
+
} catch (e) {
|
|
2018
|
+
const msg = `Failed to parse XMILE model definition:
|
|
2019
|
+
|
|
2020
|
+
${e.message}`;
|
|
2021
|
+
throw new Error(msg);
|
|
2022
|
+
}
|
|
2023
|
+
const simulationSpec = parseSimSpecs(xml.root, input);
|
|
2024
|
+
const dimensions = parseDimensionDefs(xml.root, input);
|
|
2025
|
+
const equations = parseVariableDefs(xml.root, input);
|
|
2026
|
+
return {
|
|
2027
|
+
simulationSpec,
|
|
2028
|
+
dimensions,
|
|
2029
|
+
equations
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
function parseSimSpecs(rootElem, originalXml) {
|
|
2033
|
+
const simSpecsElem = firstElemOf(rootElem, "sim_specs");
|
|
2034
|
+
if (simSpecsElem === void 0) {
|
|
2035
|
+
throw new Error(xmlError(rootElem, "<sim_specs> element is required for XMILE model definition"));
|
|
2036
|
+
}
|
|
2037
|
+
function getSimSpecValue(name, required) {
|
|
2038
|
+
const elem = firstElemOf(simSpecsElem, name);
|
|
2039
|
+
if (required && elem === void 0) {
|
|
2040
|
+
const error = new Error(xmlError(simSpecsElem, `<${name}> element is required in XMILE sim specs`));
|
|
2041
|
+
throwXmileParseError(error, originalXml, simSpecsElem, "model");
|
|
2042
|
+
}
|
|
2043
|
+
if (elem === void 0) {
|
|
2044
|
+
return void 0;
|
|
2045
|
+
}
|
|
2046
|
+
const value = Number(elem.text);
|
|
2047
|
+
if (!isNaN(value)) {
|
|
2048
|
+
return value;
|
|
2049
|
+
} else {
|
|
2050
|
+
const error = new Error(xmlError(elem, `Invalid numeric value for <${name}> element: ${elem.text}`));
|
|
2051
|
+
throwXmileParseError(error, originalXml, simSpecsElem, "model");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
const startTime = getSimSpecValue("start", true);
|
|
2055
|
+
const endTime = getSimSpecValue("stop", true);
|
|
2056
|
+
let timeStep = getSimSpecValue("dt", false);
|
|
2057
|
+
if (timeStep === void 0) {
|
|
2058
|
+
timeStep = 1;
|
|
2059
|
+
}
|
|
2060
|
+
return {
|
|
2061
|
+
startTime,
|
|
2062
|
+
endTime,
|
|
2063
|
+
timeStep
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
function parseDimensionDefs(rootElem, originalXml) {
|
|
2067
|
+
const dimensionDefs = [];
|
|
2068
|
+
const dimensionsElem = firstElemOf(rootElem, "dimensions");
|
|
2069
|
+
if (dimensionsElem) {
|
|
2070
|
+
const dimElems = elemsOf(dimensionsElem, ["dim"]);
|
|
2071
|
+
for (const dimElem of dimElems) {
|
|
2072
|
+
try {
|
|
2073
|
+
dimensionDefs.push(parseXmileDimensionDef(dimElem));
|
|
2074
|
+
} catch (e) {
|
|
2075
|
+
throwXmileParseError(e, originalXml, dimElem, "dimension");
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return dimensionDefs;
|
|
2080
|
+
}
|
|
2081
|
+
function parseVariableDefs(rootElem, originalXml) {
|
|
2082
|
+
const modelElem = firstElemOf(rootElem, "model");
|
|
2083
|
+
if (modelElem === void 0) {
|
|
2084
|
+
return [];
|
|
2085
|
+
}
|
|
2086
|
+
const equations = [];
|
|
2087
|
+
const variablesElem = firstElemOf(modelElem, "variables");
|
|
2088
|
+
if (variablesElem) {
|
|
2089
|
+
const varElems = elemsOf(variablesElem, ["aux", "stock", "flow", "gf"]);
|
|
2090
|
+
for (const varElem of varElems) {
|
|
2091
|
+
try {
|
|
2092
|
+
const eqns = parseXmileVariableDef(varElem);
|
|
2093
|
+
if (eqns) {
|
|
2094
|
+
equations.push(...eqns);
|
|
2095
|
+
}
|
|
2096
|
+
} catch (e) {
|
|
2097
|
+
throwXmileParseError(e, originalXml, varElem, "variable");
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return equations;
|
|
2102
|
+
}
|
|
2103
|
+
function throwXmileParseError(originalError, originalXml, elem, elemKind) {
|
|
2104
|
+
let linePart = "";
|
|
2105
|
+
const lineNumInOriginalXml = getLineNumber(originalXml, elem.start);
|
|
2106
|
+
if (lineNumInOriginalXml !== -1) {
|
|
2107
|
+
const cause = originalError.cause;
|
|
2108
|
+
if (cause?.code === "VensimParseError") {
|
|
2109
|
+
if (cause.line) {
|
|
2110
|
+
const lineNum = cause.line - 1 + lineNumInOriginalXml;
|
|
2111
|
+
linePart += ` at line ${lineNum}`;
|
|
2112
|
+
if (cause.column) {
|
|
2113
|
+
linePart += `, col ${cause.column}`;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
} else {
|
|
2117
|
+
linePart += ` at line ${lineNumInOriginalXml}`;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
const elemString = extractXmlLines(originalXml, elem.start, elem.end);
|
|
2121
|
+
const msg = `Failed to parse XMILE ${elemKind} definition${linePart}:
|
|
2122
|
+
${elemString}
|
|
2123
|
+
|
|
2124
|
+
Detail:
|
|
2125
|
+
${originalError.message}`;
|
|
2126
|
+
throw new Error(msg);
|
|
2127
|
+
}
|
|
2128
|
+
function getLineNumber(xmlString, byteOffset) {
|
|
2129
|
+
if (byteOffset === -1 || byteOffset >= xmlString.length) {
|
|
2130
|
+
return -1;
|
|
2131
|
+
}
|
|
2132
|
+
const substring = xmlString.substring(0, byteOffset);
|
|
2133
|
+
return substring.split("\n").length;
|
|
2134
|
+
}
|
|
2135
|
+
function extractXmlLines(originalXml, startOffset, endOffset) {
|
|
2136
|
+
if (startOffset === -1 || endOffset === -1 || startOffset >= originalXml.length || endOffset > originalXml.length) {
|
|
2137
|
+
return "[Unable to extract XML lines - invalid offsets]";
|
|
2138
|
+
}
|
|
2139
|
+
let lineStart = startOffset;
|
|
2140
|
+
while (lineStart > 0 && originalXml[lineStart - 1] !== "\n") {
|
|
2141
|
+
lineStart--;
|
|
2142
|
+
}
|
|
2143
|
+
let lineEnd = endOffset;
|
|
2144
|
+
while (lineEnd < originalXml.length && originalXml[lineEnd] !== "\n") {
|
|
2145
|
+
lineEnd++;
|
|
2146
|
+
}
|
|
2147
|
+
const relevantXml = originalXml.substring(lineStart, lineEnd);
|
|
2148
|
+
return relevantXml;
|
|
2149
|
+
}
|
|
1477
2150
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1478
2151
|
0 && (module.exports = {
|
|
2152
|
+
canonicalFunctionId,
|
|
2153
|
+
canonicalId,
|
|
2154
|
+
canonicalVarId,
|
|
1479
2155
|
debugPrintExpr,
|
|
1480
2156
|
parseVensimEquation,
|
|
1481
2157
|
parseVensimExpr,
|
|
1482
2158
|
parseVensimModel,
|
|
1483
2159
|
parseVensimSubscriptRange,
|
|
2160
|
+
parseXmileDimensionDef,
|
|
2161
|
+
parseXmileModel,
|
|
2162
|
+
parseXmileVariableDef,
|
|
2163
|
+
preprocessVensimModel,
|
|
1484
2164
|
prettyPrintExpr,
|
|
1485
2165
|
printExprStats,
|
|
1486
2166
|
reduceConditionals,
|