@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.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
// src/_shared/canonical-id.js
|
|
2
|
+
var reTrailingMark = new RegExp("\\s+!$", "g");
|
|
3
|
+
var reWhitespace = new RegExp("(\\s|_)+", "g");
|
|
4
|
+
var reSpecialChars = new RegExp(`['"\\.,\\-\\$&%\\/\\|()]`, "g");
|
|
5
|
+
function canonicalId(name) {
|
|
6
|
+
return "_" + name.trim().replace(reTrailingMark, "!").replace(reWhitespace, "_").replace(reSpecialChars, "_").toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
function canonicalVarId(name) {
|
|
9
|
+
const m = name.match(/([^[]+)(?:\[([^\]]+)\])?/);
|
|
10
|
+
if (!m) {
|
|
11
|
+
throw new Error(`Invalid variable name: ${name}`);
|
|
12
|
+
}
|
|
13
|
+
let id = canonicalId(m[1]);
|
|
14
|
+
if (m[2]) {
|
|
15
|
+
const subscripts = m[2].split(",").map((x) => canonicalId(x));
|
|
16
|
+
id += `[${subscripts.join(",")}]`;
|
|
17
|
+
}
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
function canonicalFunctionId(name) {
|
|
21
|
+
return canonicalId(name).toUpperCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
// src/ast/print-expr.ts
|
|
2
25
|
import { assertNever } from "assert-never";
|
|
3
26
|
function debugPrintExpr(expr, indent = 0) {
|
|
@@ -246,15 +269,13 @@ function fullIdForVarRef(varRef) {
|
|
|
246
269
|
// src/ast/reduce-expr.ts
|
|
247
270
|
import { assertNever as assertNever2 } from "assert-never";
|
|
248
271
|
|
|
249
|
-
// src/_shared/names.js
|
|
250
|
-
function canonicalName(name) {
|
|
251
|
-
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();
|
|
252
|
-
}
|
|
253
|
-
function cFunctionName(name) {
|
|
254
|
-
return canonicalName(name).toUpperCase();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
272
|
// src/ast/ast-builders.ts
|
|
273
|
+
function subRef(dimOrSubName) {
|
|
274
|
+
return {
|
|
275
|
+
subName: dimOrSubName,
|
|
276
|
+
subId: canonicalId(dimOrSubName)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
258
279
|
function num(value, text) {
|
|
259
280
|
return {
|
|
260
281
|
kind: "number",
|
|
@@ -283,6 +304,13 @@ function parens(expr) {
|
|
|
283
304
|
expr
|
|
284
305
|
};
|
|
285
306
|
}
|
|
307
|
+
function lookupDef(points, range) {
|
|
308
|
+
return {
|
|
309
|
+
kind: "lookup-def",
|
|
310
|
+
range,
|
|
311
|
+
points
|
|
312
|
+
};
|
|
313
|
+
}
|
|
286
314
|
function lookupCall(varRef, arg) {
|
|
287
315
|
return {
|
|
288
316
|
kind: "lookup-call",
|
|
@@ -290,6 +318,14 @@ function lookupCall(varRef, arg) {
|
|
|
290
318
|
arg
|
|
291
319
|
};
|
|
292
320
|
}
|
|
321
|
+
function call(fnName, ...args) {
|
|
322
|
+
return {
|
|
323
|
+
kind: "function-call",
|
|
324
|
+
fnName,
|
|
325
|
+
fnId: canonicalFunctionId(fnName),
|
|
326
|
+
args
|
|
327
|
+
};
|
|
328
|
+
}
|
|
293
329
|
|
|
294
330
|
// src/ast/reduce-expr.ts
|
|
295
331
|
function reduceExpr(expr, opts) {
|
|
@@ -629,7 +665,7 @@ var SubscriptRangeReader = class extends ModelVisitor {
|
|
|
629
665
|
const ids = ctx.Id();
|
|
630
666
|
if (ids.length === 1) {
|
|
631
667
|
const dimName = ids[0].getText();
|
|
632
|
-
const dimId =
|
|
668
|
+
const dimId = canonicalId(dimName);
|
|
633
669
|
super.visitSubscriptRange(ctx);
|
|
634
670
|
return {
|
|
635
671
|
dimName,
|
|
@@ -639,7 +675,7 @@ var SubscriptRangeReader = class extends ModelVisitor {
|
|
|
639
675
|
subscriptRefs: this.subscriptNames.map((subName) => {
|
|
640
676
|
return {
|
|
641
677
|
subName,
|
|
642
|
-
subId:
|
|
678
|
+
subId: canonicalId(subName)
|
|
643
679
|
};
|
|
644
680
|
}),
|
|
645
681
|
subscriptMappings: this.subscriptMappings,
|
|
@@ -647,9 +683,9 @@ var SubscriptRangeReader = class extends ModelVisitor {
|
|
|
647
683
|
};
|
|
648
684
|
} else if (ids.length === 2) {
|
|
649
685
|
const dimName = ids[0].getText();
|
|
650
|
-
const dimId =
|
|
686
|
+
const dimId = canonicalId(dimName);
|
|
651
687
|
const familyName = ids[1].getText();
|
|
652
|
-
const familyId =
|
|
688
|
+
const familyId = canonicalId(familyName);
|
|
653
689
|
return {
|
|
654
690
|
dimName,
|
|
655
691
|
dimId,
|
|
@@ -689,11 +725,11 @@ var SubscriptRangeReader = class extends ModelVisitor {
|
|
|
689
725
|
super.visitSubscriptMapping(ctx);
|
|
690
726
|
this.subscriptMappings.push({
|
|
691
727
|
toDimName,
|
|
692
|
-
toDimId:
|
|
728
|
+
toDimId: canonicalId(toDimName),
|
|
693
729
|
subscriptRefs: this.mappedSubscriptNames.map((subName) => {
|
|
694
730
|
return {
|
|
695
731
|
subName,
|
|
696
|
-
subId:
|
|
732
|
+
subId: canonicalId(subName)
|
|
697
733
|
};
|
|
698
734
|
})
|
|
699
735
|
});
|
|
@@ -703,7 +739,7 @@ var SubscriptRangeReader = class extends ModelVisitor {
|
|
|
703
739
|
}
|
|
704
740
|
visitCall(ctx) {
|
|
705
741
|
const fnName = ctx.Id().getText();
|
|
706
|
-
const fnId =
|
|
742
|
+
const fnId = canonicalFunctionId(fnName);
|
|
707
743
|
if (fnId === "_GET_DIRECT_SUBSCRIPT") {
|
|
708
744
|
super.visitCall(ctx);
|
|
709
745
|
} else {
|
|
@@ -799,7 +835,7 @@ var ExprReader = class extends ModelVisitor2 {
|
|
|
799
835
|
//
|
|
800
836
|
visitCall(ctx) {
|
|
801
837
|
const vensimFnName = ctx.Id().getText();
|
|
802
|
-
const fnId =
|
|
838
|
+
const fnId = canonicalFunctionId(vensimFnName);
|
|
803
839
|
this.callStack.push({ fn: fnId, args: [] });
|
|
804
840
|
super.visitCall(ctx);
|
|
805
841
|
const callInfo = this.callStack.pop();
|
|
@@ -822,14 +858,14 @@ var ExprReader = class extends ModelVisitor2 {
|
|
|
822
858
|
}
|
|
823
859
|
visitVar(ctx) {
|
|
824
860
|
const vensimVarName = ctx.Id().getText().trim();
|
|
825
|
-
const varId =
|
|
861
|
+
const varId = canonicalId(vensimVarName);
|
|
826
862
|
this.subscripts = void 0;
|
|
827
863
|
super.visitVar(ctx);
|
|
828
864
|
const subscriptNames = this.subscripts;
|
|
829
865
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
830
866
|
return {
|
|
831
867
|
subName: name,
|
|
832
|
-
subId:
|
|
868
|
+
subId: canonicalId(name)
|
|
833
869
|
};
|
|
834
870
|
});
|
|
835
871
|
this.subscripts = void 0;
|
|
@@ -879,7 +915,7 @@ var ExprReader = class extends ModelVisitor2 {
|
|
|
879
915
|
}
|
|
880
916
|
visitLookupCall(ctx) {
|
|
881
917
|
const lookupVarName = ctx.Id().getText();
|
|
882
|
-
const lookupVarId =
|
|
918
|
+
const lookupVarId = canonicalId(lookupVarName);
|
|
883
919
|
if (ctx.subscriptList()) {
|
|
884
920
|
ctx.subscriptList().accept(this);
|
|
885
921
|
}
|
|
@@ -887,7 +923,7 @@ var ExprReader = class extends ModelVisitor2 {
|
|
|
887
923
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
888
924
|
return {
|
|
889
925
|
subName: name,
|
|
890
|
-
subId:
|
|
926
|
+
subId: canonicalId(name)
|
|
891
927
|
};
|
|
892
928
|
});
|
|
893
929
|
this.subscripts = void 0;
|
|
@@ -1084,13 +1120,13 @@ var EquationReader = class extends ModelVisitor3 {
|
|
|
1084
1120
|
}
|
|
1085
1121
|
visitLhs(ctx) {
|
|
1086
1122
|
const lhsVarName = ctx.Id().getText();
|
|
1087
|
-
const lhsVarId =
|
|
1123
|
+
const lhsVarId = canonicalId(lhsVarName);
|
|
1088
1124
|
super.visitLhs(ctx);
|
|
1089
1125
|
const subscriptNames = this.subscripts;
|
|
1090
1126
|
const subscriptRefs = subscriptNames?.map((name) => {
|
|
1091
1127
|
return {
|
|
1092
1128
|
subName: name,
|
|
1093
|
-
subId:
|
|
1129
|
+
subId: canonicalId(name)
|
|
1094
1130
|
};
|
|
1095
1131
|
});
|
|
1096
1132
|
const exceptSubscriptSets = this.exceptSubscriptSets;
|
|
@@ -1098,7 +1134,7 @@ var EquationReader = class extends ModelVisitor3 {
|
|
|
1098
1134
|
return subscriptSet.map((name) => {
|
|
1099
1135
|
return {
|
|
1100
1136
|
subName: name,
|
|
1101
|
-
subId:
|
|
1137
|
+
subId: canonicalId(name)
|
|
1102
1138
|
};
|
|
1103
1139
|
});
|
|
1104
1140
|
});
|
|
@@ -1178,16 +1214,41 @@ function parseVensimEquation(input) {
|
|
|
1178
1214
|
|
|
1179
1215
|
// src/vensim/preprocess-vensim.ts
|
|
1180
1216
|
import split from "split-string";
|
|
1181
|
-
function preprocessVensimModel(input) {
|
|
1217
|
+
function preprocessVensimModel(input, options) {
|
|
1218
|
+
const removalKeys = options?.removalKeys;
|
|
1219
|
+
function shouldRemove(text) {
|
|
1220
|
+
if (text.includes("TABBED ARRAY")) {
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
if (removalKeys) {
|
|
1224
|
+
for (const key of removalKeys) {
|
|
1225
|
+
if (text.includes(key)) {
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
const macrosResult = removeMacros(input);
|
|
1233
|
+
input = macrosResult.processed;
|
|
1182
1234
|
const rawDefs = splitDefs(input);
|
|
1183
1235
|
const vensimDefs = [];
|
|
1236
|
+
const removedBlocks = [];
|
|
1184
1237
|
for (const rawDef of rawDefs) {
|
|
1238
|
+
if (shouldRemove(rawDef.text)) {
|
|
1239
|
+
removedBlocks.push(rawDef.text.trim() + "|");
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1185
1242
|
const vensimDef = processDef(rawDef);
|
|
1186
1243
|
if (vensimDef) {
|
|
1187
1244
|
vensimDefs.push(vensimDef);
|
|
1188
1245
|
}
|
|
1189
1246
|
}
|
|
1190
|
-
return
|
|
1247
|
+
return {
|
|
1248
|
+
defs: vensimDefs,
|
|
1249
|
+
removedMacros: macrosResult.removed,
|
|
1250
|
+
removedBlocks
|
|
1251
|
+
};
|
|
1191
1252
|
}
|
|
1192
1253
|
function splitDefs(input) {
|
|
1193
1254
|
const defTexts = split(input, { separator: "|", quotes: ['"'], keep: () => true });
|
|
@@ -1229,6 +1290,9 @@ function splitDefs(input) {
|
|
|
1229
1290
|
function splitLines(input) {
|
|
1230
1291
|
return input.split(/\r\n|\n|\r/);
|
|
1231
1292
|
}
|
|
1293
|
+
function splitExceptInQuoted(input, sep) {
|
|
1294
|
+
return split(input, { separator: sep, quotes: ['"'] });
|
|
1295
|
+
}
|
|
1232
1296
|
function processBackslashes(input) {
|
|
1233
1297
|
const inputLines = splitLines(input);
|
|
1234
1298
|
let output = "";
|
|
@@ -1274,22 +1338,27 @@ function replaceDelimitedStrings(str, open, close, newStr) {
|
|
|
1274
1338
|
function reduceWhitespace(input) {
|
|
1275
1339
|
return input.replace(/\s\s+/g, " ").trim();
|
|
1276
1340
|
}
|
|
1341
|
+
var reWhitespace2 = new RegExp("(\\s|_)+", "g");
|
|
1277
1342
|
function keyForDef(def) {
|
|
1278
1343
|
let key = def;
|
|
1279
1344
|
key = key.replace(/:INTERPOLATE:/g, "");
|
|
1345
|
+
let kind;
|
|
1280
1346
|
if (key.includes("=")) {
|
|
1347
|
+
kind = "eqn";
|
|
1281
1348
|
key = key.split("=")[0].trim();
|
|
1282
1349
|
} else if (key.includes(":")) {
|
|
1350
|
+
kind = "dim";
|
|
1283
1351
|
key = key.split(":")[0].trim();
|
|
1284
1352
|
} else {
|
|
1353
|
+
kind = "decl";
|
|
1285
1354
|
}
|
|
1355
|
+
key = splitExceptInQuoted(key, "(")[0];
|
|
1286
1356
|
key = key.replace(/"/g, "");
|
|
1287
|
-
key = key.split("(")[0];
|
|
1288
1357
|
key = key.trim();
|
|
1289
|
-
key = key.replace(/\
|
|
1290
|
-
key = key.replace(
|
|
1358
|
+
key = key.replace(/(?<=\[).*?(?=\])/g, (match) => match.replace(/\s/g, ""));
|
|
1359
|
+
key = key.replace(reWhitespace2, "_");
|
|
1291
1360
|
key = key.toLowerCase();
|
|
1292
|
-
return key;
|
|
1361
|
+
return { key, kind };
|
|
1293
1362
|
}
|
|
1294
1363
|
function processDef(rawDef) {
|
|
1295
1364
|
let input = rawDef.text;
|
|
@@ -1307,7 +1376,7 @@ function processDef(rawDef) {
|
|
|
1307
1376
|
${input}`);
|
|
1308
1377
|
}
|
|
1309
1378
|
const rawDefText = reduceWhitespace(parts[0]);
|
|
1310
|
-
const key = keyForDef(rawDefText);
|
|
1379
|
+
const { key, kind } = keyForDef(rawDefText);
|
|
1311
1380
|
const def = `${rawDefText} ~~|`;
|
|
1312
1381
|
const units = reduceWhitespace(parts[1]);
|
|
1313
1382
|
const comment = reduceWhitespace(parts[2]);
|
|
@@ -1315,12 +1384,25 @@ ${input}`);
|
|
|
1315
1384
|
return {
|
|
1316
1385
|
key,
|
|
1317
1386
|
def,
|
|
1387
|
+
kind,
|
|
1318
1388
|
line: rawDef.line,
|
|
1319
1389
|
units,
|
|
1320
1390
|
comment,
|
|
1321
1391
|
...group ? { group } : {}
|
|
1322
1392
|
};
|
|
1323
1393
|
}
|
|
1394
|
+
function removeMacros(input) {
|
|
1395
|
+
const removed = [];
|
|
1396
|
+
const processed = input.replace(/:MACRO:.*:END OF MACRO:/gms, (match) => {
|
|
1397
|
+
removed.push(match);
|
|
1398
|
+
const numBreaks = match.split(/\r\n|\n|\r/gms).length - 1;
|
|
1399
|
+
return numBreaks > 0 ? "\n".repeat(numBreaks) : "";
|
|
1400
|
+
});
|
|
1401
|
+
return {
|
|
1402
|
+
processed,
|
|
1403
|
+
removed
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1324
1406
|
|
|
1325
1407
|
// src/vensim/impl/model-reader.js
|
|
1326
1408
|
import { ModelVisitor as ModelVisitor4 } from "antlr4-vensim";
|
|
@@ -1379,7 +1461,7 @@ var ModelReader = class extends ModelVisitor4 {
|
|
|
1379
1461
|
function parseVensimModel(input, context, sort = false) {
|
|
1380
1462
|
const dimensions = [];
|
|
1381
1463
|
const equations = [];
|
|
1382
|
-
const defs = preprocessVensimModel(input);
|
|
1464
|
+
const { defs } = preprocessVensimModel(input);
|
|
1383
1465
|
if (sort) {
|
|
1384
1466
|
defs.sort((a, b) => {
|
|
1385
1467
|
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
@@ -1430,12 +1512,603 @@ Detail:
|
|
|
1430
1512
|
equations
|
|
1431
1513
|
};
|
|
1432
1514
|
}
|
|
1515
|
+
|
|
1516
|
+
// src/xmile/xml.ts
|
|
1517
|
+
import { XmlNode } from "@rgrove/parse-xml";
|
|
1518
|
+
function firstElemOf(parent, tagName) {
|
|
1519
|
+
return parent?.children.find((n) => {
|
|
1520
|
+
if (n.type === XmlNode.TYPE_ELEMENT) {
|
|
1521
|
+
const e = n;
|
|
1522
|
+
return e.name === tagName;
|
|
1523
|
+
} else {
|
|
1524
|
+
return void 0;
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
function firstTextOf(parent) {
|
|
1529
|
+
return parent?.children.find((n) => {
|
|
1530
|
+
return n.type === XmlNode.TYPE_TEXT;
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
function elemsOf(parent, tagNames) {
|
|
1534
|
+
if (parent === void 0) {
|
|
1535
|
+
return [];
|
|
1536
|
+
}
|
|
1537
|
+
const elems = [];
|
|
1538
|
+
for (const n of parent.children) {
|
|
1539
|
+
if (n.type === XmlNode.TYPE_ELEMENT) {
|
|
1540
|
+
const e = n;
|
|
1541
|
+
if (tagNames.includes(e.name)) {
|
|
1542
|
+
elems.push(e);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return elems;
|
|
1547
|
+
}
|
|
1548
|
+
function xmlError(elem, msg) {
|
|
1549
|
+
return `${msg}: ${JSON.stringify(elem.toJSON(), null, 2)}`;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/xmile/parse-xmile-dimension-def.ts
|
|
1553
|
+
function parseXmileDimensionDef(dimElem) {
|
|
1554
|
+
const dimName = dimElem.attributes?.name;
|
|
1555
|
+
if (dimName === void 0) {
|
|
1556
|
+
throw new Error(xmlError(dimElem, "<dim> name attribute is required for dimension definition"));
|
|
1557
|
+
}
|
|
1558
|
+
const elemElems = elemsOf(dimElem, ["elem"]);
|
|
1559
|
+
if (elemElems.length === 0) {
|
|
1560
|
+
throw new Error(xmlError(dimElem, "<dim> must contain one or more <elem> elements"));
|
|
1561
|
+
}
|
|
1562
|
+
const subscriptRefs = [];
|
|
1563
|
+
for (const elem of elemElems) {
|
|
1564
|
+
const subName = elem.attributes?.name;
|
|
1565
|
+
if (subName === void 0) {
|
|
1566
|
+
throw new Error(xmlError(dimElem, "<elem> name attribute is required for dimension element definition"));
|
|
1567
|
+
}
|
|
1568
|
+
const subId = canonicalId(subName);
|
|
1569
|
+
subscriptRefs.push({
|
|
1570
|
+
subId,
|
|
1571
|
+
subName
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
const comment = firstElemOf(dimElem, "doc")?.text || "";
|
|
1575
|
+
const dimId = canonicalId(dimName);
|
|
1576
|
+
return {
|
|
1577
|
+
dimName,
|
|
1578
|
+
dimId,
|
|
1579
|
+
// TODO: For Vensim `DimA <-> DimB` aliases, the family name would be `DimB`
|
|
1580
|
+
familyName: dimName,
|
|
1581
|
+
familyId: dimId,
|
|
1582
|
+
subscriptRefs,
|
|
1583
|
+
// TODO: Does XMILE support mappings?
|
|
1584
|
+
subscriptMappings: [],
|
|
1585
|
+
comment
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// src/xmile/parse-xmile-model.ts
|
|
1590
|
+
import { parseXml } from "@rgrove/parse-xml";
|
|
1591
|
+
|
|
1592
|
+
// src/xmile/parse-xmile-variable-def.ts
|
|
1593
|
+
function parseXmileVariableDef(varElem) {
|
|
1594
|
+
let varName = parseRequiredAttr(varElem, varElem, "name");
|
|
1595
|
+
varName = varName.replace(/\\n/g, " ");
|
|
1596
|
+
const varId = canonicalId(varName);
|
|
1597
|
+
const units = firstElemOf(varElem, "units")?.text || "";
|
|
1598
|
+
const comment = firstElemOf(varElem, "doc")?.text || "";
|
|
1599
|
+
function exprEquation(subscriptRefs, expr) {
|
|
1600
|
+
return {
|
|
1601
|
+
lhs: {
|
|
1602
|
+
varDef: {
|
|
1603
|
+
kind: "variable-def",
|
|
1604
|
+
varName,
|
|
1605
|
+
varId,
|
|
1606
|
+
subscriptRefs
|
|
1607
|
+
}
|
|
1608
|
+
},
|
|
1609
|
+
rhs: {
|
|
1610
|
+
kind: "expr",
|
|
1611
|
+
expr
|
|
1612
|
+
},
|
|
1613
|
+
units,
|
|
1614
|
+
comment
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function lookupEquation(subscriptRefs, lookup) {
|
|
1618
|
+
return {
|
|
1619
|
+
lhs: {
|
|
1620
|
+
varDef: {
|
|
1621
|
+
kind: "variable-def",
|
|
1622
|
+
varName,
|
|
1623
|
+
varId,
|
|
1624
|
+
subscriptRefs
|
|
1625
|
+
}
|
|
1626
|
+
},
|
|
1627
|
+
rhs: {
|
|
1628
|
+
kind: "lookup",
|
|
1629
|
+
lookupDef: lookup
|
|
1630
|
+
},
|
|
1631
|
+
units,
|
|
1632
|
+
comment
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (varElem.name === "gf") {
|
|
1636
|
+
const lookup = parseGfElem(varElem, varElem);
|
|
1637
|
+
return [lookupEquation(void 0, lookup)];
|
|
1638
|
+
}
|
|
1639
|
+
const dimensionsElem = firstElemOf(varElem, "dimensions");
|
|
1640
|
+
const equationDefs = [];
|
|
1641
|
+
if (dimensionsElem === void 0) {
|
|
1642
|
+
const gfElem = firstElemOf(varElem, "gf");
|
|
1643
|
+
if (gfElem) {
|
|
1644
|
+
if (varElem.name !== "flow" && varElem.name !== "aux") {
|
|
1645
|
+
throw new Error(xmlError(varElem, "<gf> is only allowed for <flow> and <aux> variables"));
|
|
1646
|
+
}
|
|
1647
|
+
const lookup = parseGfElem(varElem, gfElem);
|
|
1648
|
+
equationDefs.push(lookupEquation(void 0, lookup));
|
|
1649
|
+
} else {
|
|
1650
|
+
const expr = parseEqnElem(varElem, varElem);
|
|
1651
|
+
if (expr) {
|
|
1652
|
+
equationDefs.push(exprEquation(void 0, expr));
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
} else {
|
|
1656
|
+
const dimElems = elemsOf(dimensionsElem, ["dim"]);
|
|
1657
|
+
const dimNames = [];
|
|
1658
|
+
for (const dimElem of dimElems) {
|
|
1659
|
+
const dimName = dimElem.attributes?.name;
|
|
1660
|
+
if (dimName === void 0) {
|
|
1661
|
+
throw new Error(xmlError(varElem, "<dim> name attribute is required in <dimensions> for variable definition"));
|
|
1662
|
+
}
|
|
1663
|
+
dimNames.push(dimName);
|
|
1664
|
+
}
|
|
1665
|
+
const elementElems = elemsOf(varElem, ["element"]);
|
|
1666
|
+
if (elementElems.length === 0) {
|
|
1667
|
+
const dimRefs = dimNames.map(subRef);
|
|
1668
|
+
const expr = parseEqnElem(varElem, varElem);
|
|
1669
|
+
if (expr) {
|
|
1670
|
+
equationDefs.push(exprEquation(dimRefs, expr));
|
|
1671
|
+
}
|
|
1672
|
+
} else {
|
|
1673
|
+
for (const elementElem of elementElems) {
|
|
1674
|
+
const subscriptAttr = elementElem.attributes?.subscript;
|
|
1675
|
+
if (subscriptAttr === void 0) {
|
|
1676
|
+
throw new Error(xmlError(varElem, "<element> subscript attribute is required in variable definition"));
|
|
1677
|
+
}
|
|
1678
|
+
const subscriptNames = subscriptAttr.split(",").map((s) => s.trim());
|
|
1679
|
+
const subRefs = [];
|
|
1680
|
+
for (const subscriptName of subscriptNames) {
|
|
1681
|
+
if (!isNaN(parseInt(subscriptAttr))) {
|
|
1682
|
+
throw new Error(xmlError(varElem, "Numeric subscript indices are not currently supported"));
|
|
1683
|
+
}
|
|
1684
|
+
subRefs.push(subRef(subscriptName));
|
|
1685
|
+
}
|
|
1686
|
+
const expr = parseEqnElem(varElem, elementElem);
|
|
1687
|
+
if (expr) {
|
|
1688
|
+
equationDefs.push(exprEquation(subRefs, expr));
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return equationDefs;
|
|
1694
|
+
}
|
|
1695
|
+
function parseEqnElem(varElem, parentElem) {
|
|
1696
|
+
const varTagName = varElem.name;
|
|
1697
|
+
const eqnElem = firstElemOf(parentElem, "eqn");
|
|
1698
|
+
const eqnText = eqnElem ? firstTextOf(eqnElem) : void 0;
|
|
1699
|
+
switch (varTagName) {
|
|
1700
|
+
case "aux": {
|
|
1701
|
+
if (eqnText === void 0) {
|
|
1702
|
+
return void 0;
|
|
1703
|
+
}
|
|
1704
|
+
const initEqnElem = firstElemOf(parentElem, "init_eqn");
|
|
1705
|
+
const initEqnText = initEqnElem ? firstTextOf(initEqnElem) : void 0;
|
|
1706
|
+
if (initEqnText !== void 0) {
|
|
1707
|
+
const eqnExpr = parseExpr(eqnText.text);
|
|
1708
|
+
const initEqnExpr = parseExpr(initEqnText.text);
|
|
1709
|
+
return call("ACTIVE INITIAL", eqnExpr, initEqnExpr);
|
|
1710
|
+
}
|
|
1711
|
+
return parseExpr(eqnText.text);
|
|
1712
|
+
}
|
|
1713
|
+
case "stock": {
|
|
1714
|
+
if (eqnText === void 0) {
|
|
1715
|
+
throw new Error(xmlError(varElem, "An <eqn> is required for a <stock> variable"));
|
|
1716
|
+
}
|
|
1717
|
+
const inflowElems = elemsOf(parentElem, ["inflow"]);
|
|
1718
|
+
const outflowElems = elemsOf(parentElem, ["outflow"]);
|
|
1719
|
+
const inflowTexts = inflowElems.map((inflowElem) => {
|
|
1720
|
+
const inflowText = firstTextOf(inflowElem);
|
|
1721
|
+
if (inflowText === void 0) {
|
|
1722
|
+
throw new Error(xmlError(varElem, "An <inflow> must be non-empty for a <stock> variable"));
|
|
1723
|
+
}
|
|
1724
|
+
return inflowText.text;
|
|
1725
|
+
});
|
|
1726
|
+
const outflowTexts = outflowElems.map((outflowElem) => {
|
|
1727
|
+
const outflowText = firstTextOf(outflowElem);
|
|
1728
|
+
if (outflowText === void 0) {
|
|
1729
|
+
throw new Error(xmlError(varElem, "An <outflow> must be non-empty for a <stock> variable"));
|
|
1730
|
+
}
|
|
1731
|
+
return outflowText.text;
|
|
1732
|
+
});
|
|
1733
|
+
if (firstElemOf(parentElem, "conveyor")) {
|
|
1734
|
+
throw new Error(xmlError(varElem, "Currently <conveyor> is not supported for a <stock> variable"));
|
|
1735
|
+
}
|
|
1736
|
+
if (firstElemOf(parentElem, "queue")) {
|
|
1737
|
+
throw new Error(xmlError(varElem, "Currently <queue> is not supported for a <stock> variable"));
|
|
1738
|
+
}
|
|
1739
|
+
const inflowParts = inflowTexts.join(" + ");
|
|
1740
|
+
let outflowParts = outflowTexts.join(" - ");
|
|
1741
|
+
if (outflowTexts.length > 0) {
|
|
1742
|
+
if (inflowParts.length > 0) {
|
|
1743
|
+
outflowParts = `- ${outflowParts}`;
|
|
1744
|
+
} else {
|
|
1745
|
+
outflowParts = `-${outflowParts}`;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
const flowsExpr = parseExpr(`${inflowParts} ${outflowParts}`);
|
|
1749
|
+
const initExpr = parseExpr(eqnText.text);
|
|
1750
|
+
return call("INTEG", flowsExpr, initExpr);
|
|
1751
|
+
}
|
|
1752
|
+
case "flow":
|
|
1753
|
+
if (eqnText === void 0) {
|
|
1754
|
+
throw new Error(xmlError(varElem, "Currently <eqn> or <gf> is required for a <flow> variable"));
|
|
1755
|
+
}
|
|
1756
|
+
if (firstElemOf(parentElem, "multiplier")) {
|
|
1757
|
+
throw new Error(xmlError(varElem, "Currently <multiplier> is not supported for a <flow> variable"));
|
|
1758
|
+
}
|
|
1759
|
+
if (firstElemOf(parentElem, "overflow")) {
|
|
1760
|
+
throw new Error(xmlError(varElem, "Currently <overflow> is not supported for a <flow> variable"));
|
|
1761
|
+
}
|
|
1762
|
+
if (firstElemOf(parentElem, "leak")) {
|
|
1763
|
+
throw new Error(xmlError(varElem, "Currently <leak> is not supported for a <flow> variable"));
|
|
1764
|
+
}
|
|
1765
|
+
return parseExpr(eqnText.text);
|
|
1766
|
+
default:
|
|
1767
|
+
throw new Error(xmlError(varElem, `Unhandled variable type '${varTagName}'`));
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function parseExpr(exprText) {
|
|
1771
|
+
exprText = convertConditionalExpressions(exprText);
|
|
1772
|
+
exprText = exprText.replace(/\[([^\]]*)\*([^\]]*)\]/g, "[$1_SDE_WILDCARD_!$2]");
|
|
1773
|
+
return parseVensimExpr(exprText);
|
|
1774
|
+
}
|
|
1775
|
+
function parseGfElem(varElem, gfElem) {
|
|
1776
|
+
const typeAttr = parseOptionalAttr(gfElem, "type");
|
|
1777
|
+
if (typeAttr && typeAttr !== "continuous") {
|
|
1778
|
+
throw new Error(xmlError(varElem, 'Currently "continuous" is the only type supported for <gf>'));
|
|
1779
|
+
}
|
|
1780
|
+
const yptsElem = firstElemOf(gfElem, "ypts");
|
|
1781
|
+
if (yptsElem === void 0) {
|
|
1782
|
+
throw new Error(xmlError(varElem, "<ypts> must be defined for a <gf>"));
|
|
1783
|
+
}
|
|
1784
|
+
const ypts = parseGfPts(varElem, yptsElem);
|
|
1785
|
+
if (ypts.length === 0) {
|
|
1786
|
+
throw new Error(xmlError(varElem, "<ypts> must have at least one element"));
|
|
1787
|
+
}
|
|
1788
|
+
const xptsElem = firstElemOf(gfElem, "xpts");
|
|
1789
|
+
const xscaleElem = firstElemOf(gfElem, "xscale");
|
|
1790
|
+
if (xptsElem && xscaleElem) {
|
|
1791
|
+
throw new Error(xmlError(varElem, "<gf> must contain <xpts> or <xscale> but not both"));
|
|
1792
|
+
} else if (xptsElem === void 0 && xscaleElem === void 0) {
|
|
1793
|
+
throw new Error(xmlError(varElem, "<gf> must contain either <xpts> or <xscale>"));
|
|
1794
|
+
}
|
|
1795
|
+
let xpts;
|
|
1796
|
+
if (xptsElem) {
|
|
1797
|
+
xpts = parseGfPts(varElem, xptsElem);
|
|
1798
|
+
if (xpts.length === 0) {
|
|
1799
|
+
throw new Error(xmlError(varElem, "<xpts> must have at least one element"));
|
|
1800
|
+
}
|
|
1801
|
+
} else {
|
|
1802
|
+
const xMin = parseFloatAttr(varElem, xscaleElem, "min");
|
|
1803
|
+
const xMax = parseFloatAttr(varElem, xscaleElem, "max");
|
|
1804
|
+
if (xMin > xMax) {
|
|
1805
|
+
throw new Error(xmlError(varElem, "<xscale> max attribute must be > min attribute"));
|
|
1806
|
+
}
|
|
1807
|
+
xpts = Array(ypts.length);
|
|
1808
|
+
const xRange = xMax - xMin;
|
|
1809
|
+
if (ypts.length === 1) {
|
|
1810
|
+
xpts[0] = 0;
|
|
1811
|
+
} else {
|
|
1812
|
+
for (let i = 0; i < ypts.length; i++) {
|
|
1813
|
+
const frac = i / (ypts.length - 1);
|
|
1814
|
+
xpts[i] = xMin + xRange * frac;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (xpts.length !== ypts.length) {
|
|
1819
|
+
throw new Error(xmlError(varElem, "<xpts> and <ypts> must have the same number of elements"));
|
|
1820
|
+
}
|
|
1821
|
+
const points = [];
|
|
1822
|
+
for (let i = 0; i < xpts.length; i++) {
|
|
1823
|
+
points.push([xpts[i], ypts[i]]);
|
|
1824
|
+
}
|
|
1825
|
+
return lookupDef(points);
|
|
1826
|
+
}
|
|
1827
|
+
function parseGfPts(varElem, ptsElem) {
|
|
1828
|
+
const ptsText = firstTextOf(ptsElem)?.text;
|
|
1829
|
+
if (ptsText === void 0) {
|
|
1830
|
+
return [];
|
|
1831
|
+
}
|
|
1832
|
+
const sep = ptsElem.attributes?.sep || ",";
|
|
1833
|
+
const elems = ptsText.split(sep);
|
|
1834
|
+
const nums = [];
|
|
1835
|
+
for (const elem of elems) {
|
|
1836
|
+
const numText = elem.trim();
|
|
1837
|
+
const num2 = parseFloat(numText);
|
|
1838
|
+
if (isNaN(num2)) {
|
|
1839
|
+
console.log(JSON.stringify(ptsElem));
|
|
1840
|
+
throw new Error(xmlError(varElem, `Invalid number value '${numText}' in <${ptsElem.name}>'`));
|
|
1841
|
+
}
|
|
1842
|
+
nums.push(num2);
|
|
1843
|
+
}
|
|
1844
|
+
return nums;
|
|
1845
|
+
}
|
|
1846
|
+
function parseRequiredAttr(varElem, elem, attrName) {
|
|
1847
|
+
let s = elem.attributes && elem.attributes[attrName];
|
|
1848
|
+
s = s?.trim();
|
|
1849
|
+
if (s === void 0 || s.length === 0) {
|
|
1850
|
+
throw new Error(xmlError(varElem, `<${elem.name}> ${attrName} attribute is required`));
|
|
1851
|
+
}
|
|
1852
|
+
return s;
|
|
1853
|
+
}
|
|
1854
|
+
function parseOptionalAttr(elem, attrName) {
|
|
1855
|
+
const s = elem.attributes && elem.attributes[attrName];
|
|
1856
|
+
return s?.trim();
|
|
1857
|
+
}
|
|
1858
|
+
function parseFloatAttr(varElem, elem, attrName) {
|
|
1859
|
+
const s = parseRequiredAttr(varElem, elem, attrName);
|
|
1860
|
+
const num2 = parseFloat(s);
|
|
1861
|
+
if (isNaN(num2)) {
|
|
1862
|
+
throw new Error(xmlError(varElem, `Invalid number value '${s}' for <${elem.name}> ${attrName} attribute'`));
|
|
1863
|
+
}
|
|
1864
|
+
return num2;
|
|
1865
|
+
}
|
|
1866
|
+
function convertConditionalExpressions(exprText) {
|
|
1867
|
+
const normalizedText = exprText.trim().replace(/\s+/g, " ");
|
|
1868
|
+
const ifMatch = normalizedText.match(/\bIF\s+(.+)$/i);
|
|
1869
|
+
if (!ifMatch) {
|
|
1870
|
+
return exprText;
|
|
1871
|
+
}
|
|
1872
|
+
const ifIndex = normalizedText.search(/\bIF\s+/i);
|
|
1873
|
+
const beforeIf = normalizedText.substring(0, ifIndex);
|
|
1874
|
+
const afterIf = normalizedText.substring(ifIndex + 3).trim();
|
|
1875
|
+
const thenMatch = afterIf.match(/^(.+?)\s+THEN\s+(.+)$/i);
|
|
1876
|
+
if (!thenMatch) {
|
|
1877
|
+
return exprText;
|
|
1878
|
+
}
|
|
1879
|
+
const condition = thenMatch[1].trim();
|
|
1880
|
+
const afterThen = thenMatch[2];
|
|
1881
|
+
let elseIndex = -1;
|
|
1882
|
+
let parenCount = 0;
|
|
1883
|
+
let inQuotes = false;
|
|
1884
|
+
let quoteChar = "";
|
|
1885
|
+
for (let i = 0; i < afterThen.length; i++) {
|
|
1886
|
+
const char = afterThen[i];
|
|
1887
|
+
if ((char === '"' || char === "'") && (i === 0 || afterThen[i - 1] !== "\\")) {
|
|
1888
|
+
if (!inQuotes) {
|
|
1889
|
+
inQuotes = true;
|
|
1890
|
+
quoteChar = char;
|
|
1891
|
+
} else if (char === quoteChar) {
|
|
1892
|
+
inQuotes = false;
|
|
1893
|
+
quoteChar = "";
|
|
1894
|
+
}
|
|
1895
|
+
continue;
|
|
1896
|
+
}
|
|
1897
|
+
if (inQuotes) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (char === "(") {
|
|
1901
|
+
parenCount++;
|
|
1902
|
+
} else if (char === ")") {
|
|
1903
|
+
parenCount--;
|
|
1904
|
+
}
|
|
1905
|
+
if (parenCount === 0 && !inQuotes) {
|
|
1906
|
+
const elseMatch = afterThen.substring(i).match(/^ELSE\s+(.+)$/i);
|
|
1907
|
+
if (elseMatch) {
|
|
1908
|
+
elseIndex = i;
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (elseIndex === -1) {
|
|
1914
|
+
return exprText;
|
|
1915
|
+
}
|
|
1916
|
+
const trueExpr = afterThen.substring(0, elseIndex).trim();
|
|
1917
|
+
let falseExpr = afterThen.substring(elseIndex + 5).trim();
|
|
1918
|
+
let endIndex = -1;
|
|
1919
|
+
parenCount = 0;
|
|
1920
|
+
inQuotes = false;
|
|
1921
|
+
quoteChar = "";
|
|
1922
|
+
for (let i = 0; i < falseExpr.length; i++) {
|
|
1923
|
+
const char = falseExpr[i];
|
|
1924
|
+
if ((char === '"' || char === "'") && (i === 0 || falseExpr[i - 1] !== "\\")) {
|
|
1925
|
+
if (!inQuotes) {
|
|
1926
|
+
inQuotes = true;
|
|
1927
|
+
quoteChar = char;
|
|
1928
|
+
} else if (char === quoteChar) {
|
|
1929
|
+
inQuotes = false;
|
|
1930
|
+
quoteChar = "";
|
|
1931
|
+
}
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (inQuotes) {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
if (char === "(") {
|
|
1938
|
+
parenCount++;
|
|
1939
|
+
} else if (char === ")") {
|
|
1940
|
+
if (parenCount === 0) {
|
|
1941
|
+
endIndex = i;
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
parenCount--;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (endIndex !== -1) {
|
|
1948
|
+
falseExpr = falseExpr.substring(0, endIndex).trim();
|
|
1949
|
+
}
|
|
1950
|
+
const convertedTrueExpr = convertConditionalExpressions(trueExpr);
|
|
1951
|
+
const convertedFalseExpr = convertConditionalExpressions(falseExpr);
|
|
1952
|
+
const convertedCondition = condition.replace(/(?<!".*?)\b AND \b(?!.*?")/gi, " :AND: ").replace(/(?<!".*?)\b OR \b(?!.*?")/gi, " :OR: ").replace(/(?<!".*?)\b\s?NOT \b(?!.*?")/gi, " :NOT: ").replace(/^\((.+)\)$/, "$1");
|
|
1953
|
+
const elseStartInAfterIf = afterIf.indexOf(" ELSE ") + 6;
|
|
1954
|
+
const falseExprStartInAfterIf = elseStartInAfterIf;
|
|
1955
|
+
const falseExprEndInAfterIf = falseExprStartInAfterIf + falseExpr.length;
|
|
1956
|
+
const conditionalEndInNormalizedText = ifIndex + 3 + falseExprEndInAfterIf;
|
|
1957
|
+
const afterConditional = normalizedText.substring(conditionalEndInNormalizedText).trim();
|
|
1958
|
+
return `${beforeIf}IF THEN ELSE(${convertedCondition}, ${convertedTrueExpr}, ${convertedFalseExpr})${afterConditional}`;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/xmile/parse-xmile-model.ts
|
|
1962
|
+
function parseXmileModel(input) {
|
|
1963
|
+
let xml;
|
|
1964
|
+
try {
|
|
1965
|
+
xml = parseXml(input, { includeOffsets: true });
|
|
1966
|
+
} catch (e) {
|
|
1967
|
+
const msg = `Failed to parse XMILE model definition:
|
|
1968
|
+
|
|
1969
|
+
${e.message}`;
|
|
1970
|
+
throw new Error(msg);
|
|
1971
|
+
}
|
|
1972
|
+
const simulationSpec = parseSimSpecs(xml.root, input);
|
|
1973
|
+
const dimensions = parseDimensionDefs(xml.root, input);
|
|
1974
|
+
const equations = parseVariableDefs(xml.root, input);
|
|
1975
|
+
return {
|
|
1976
|
+
simulationSpec,
|
|
1977
|
+
dimensions,
|
|
1978
|
+
equations
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
function parseSimSpecs(rootElem, originalXml) {
|
|
1982
|
+
const simSpecsElem = firstElemOf(rootElem, "sim_specs");
|
|
1983
|
+
if (simSpecsElem === void 0) {
|
|
1984
|
+
throw new Error(xmlError(rootElem, "<sim_specs> element is required for XMILE model definition"));
|
|
1985
|
+
}
|
|
1986
|
+
function getSimSpecValue(name, required) {
|
|
1987
|
+
const elem = firstElemOf(simSpecsElem, name);
|
|
1988
|
+
if (required && elem === void 0) {
|
|
1989
|
+
const error = new Error(xmlError(simSpecsElem, `<${name}> element is required in XMILE sim specs`));
|
|
1990
|
+
throwXmileParseError(error, originalXml, simSpecsElem, "model");
|
|
1991
|
+
}
|
|
1992
|
+
if (elem === void 0) {
|
|
1993
|
+
return void 0;
|
|
1994
|
+
}
|
|
1995
|
+
const value = Number(elem.text);
|
|
1996
|
+
if (!isNaN(value)) {
|
|
1997
|
+
return value;
|
|
1998
|
+
} else {
|
|
1999
|
+
const error = new Error(xmlError(elem, `Invalid numeric value for <${name}> element: ${elem.text}`));
|
|
2000
|
+
throwXmileParseError(error, originalXml, simSpecsElem, "model");
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
const startTime = getSimSpecValue("start", true);
|
|
2004
|
+
const endTime = getSimSpecValue("stop", true);
|
|
2005
|
+
let timeStep = getSimSpecValue("dt", false);
|
|
2006
|
+
if (timeStep === void 0) {
|
|
2007
|
+
timeStep = 1;
|
|
2008
|
+
}
|
|
2009
|
+
return {
|
|
2010
|
+
startTime,
|
|
2011
|
+
endTime,
|
|
2012
|
+
timeStep
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
function parseDimensionDefs(rootElem, originalXml) {
|
|
2016
|
+
const dimensionDefs = [];
|
|
2017
|
+
const dimensionsElem = firstElemOf(rootElem, "dimensions");
|
|
2018
|
+
if (dimensionsElem) {
|
|
2019
|
+
const dimElems = elemsOf(dimensionsElem, ["dim"]);
|
|
2020
|
+
for (const dimElem of dimElems) {
|
|
2021
|
+
try {
|
|
2022
|
+
dimensionDefs.push(parseXmileDimensionDef(dimElem));
|
|
2023
|
+
} catch (e) {
|
|
2024
|
+
throwXmileParseError(e, originalXml, dimElem, "dimension");
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return dimensionDefs;
|
|
2029
|
+
}
|
|
2030
|
+
function parseVariableDefs(rootElem, originalXml) {
|
|
2031
|
+
const modelElem = firstElemOf(rootElem, "model");
|
|
2032
|
+
if (modelElem === void 0) {
|
|
2033
|
+
return [];
|
|
2034
|
+
}
|
|
2035
|
+
const equations = [];
|
|
2036
|
+
const variablesElem = firstElemOf(modelElem, "variables");
|
|
2037
|
+
if (variablesElem) {
|
|
2038
|
+
const varElems = elemsOf(variablesElem, ["aux", "stock", "flow", "gf"]);
|
|
2039
|
+
for (const varElem of varElems) {
|
|
2040
|
+
try {
|
|
2041
|
+
const eqns = parseXmileVariableDef(varElem);
|
|
2042
|
+
if (eqns) {
|
|
2043
|
+
equations.push(...eqns);
|
|
2044
|
+
}
|
|
2045
|
+
} catch (e) {
|
|
2046
|
+
throwXmileParseError(e, originalXml, varElem, "variable");
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return equations;
|
|
2051
|
+
}
|
|
2052
|
+
function throwXmileParseError(originalError, originalXml, elem, elemKind) {
|
|
2053
|
+
let linePart = "";
|
|
2054
|
+
const lineNumInOriginalXml = getLineNumber(originalXml, elem.start);
|
|
2055
|
+
if (lineNumInOriginalXml !== -1) {
|
|
2056
|
+
const cause = originalError.cause;
|
|
2057
|
+
if (cause?.code === "VensimParseError") {
|
|
2058
|
+
if (cause.line) {
|
|
2059
|
+
const lineNum = cause.line - 1 + lineNumInOriginalXml;
|
|
2060
|
+
linePart += ` at line ${lineNum}`;
|
|
2061
|
+
if (cause.column) {
|
|
2062
|
+
linePart += `, col ${cause.column}`;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
} else {
|
|
2066
|
+
linePart += ` at line ${lineNumInOriginalXml}`;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
const elemString = extractXmlLines(originalXml, elem.start, elem.end);
|
|
2070
|
+
const msg = `Failed to parse XMILE ${elemKind} definition${linePart}:
|
|
2071
|
+
${elemString}
|
|
2072
|
+
|
|
2073
|
+
Detail:
|
|
2074
|
+
${originalError.message}`;
|
|
2075
|
+
throw new Error(msg);
|
|
2076
|
+
}
|
|
2077
|
+
function getLineNumber(xmlString, byteOffset) {
|
|
2078
|
+
if (byteOffset === -1 || byteOffset >= xmlString.length) {
|
|
2079
|
+
return -1;
|
|
2080
|
+
}
|
|
2081
|
+
const substring = xmlString.substring(0, byteOffset);
|
|
2082
|
+
return substring.split("\n").length;
|
|
2083
|
+
}
|
|
2084
|
+
function extractXmlLines(originalXml, startOffset, endOffset) {
|
|
2085
|
+
if (startOffset === -1 || endOffset === -1 || startOffset >= originalXml.length || endOffset > originalXml.length) {
|
|
2086
|
+
return "[Unable to extract XML lines - invalid offsets]";
|
|
2087
|
+
}
|
|
2088
|
+
let lineStart = startOffset;
|
|
2089
|
+
while (lineStart > 0 && originalXml[lineStart - 1] !== "\n") {
|
|
2090
|
+
lineStart--;
|
|
2091
|
+
}
|
|
2092
|
+
let lineEnd = endOffset;
|
|
2093
|
+
while (lineEnd < originalXml.length && originalXml[lineEnd] !== "\n") {
|
|
2094
|
+
lineEnd++;
|
|
2095
|
+
}
|
|
2096
|
+
const relevantXml = originalXml.substring(lineStart, lineEnd);
|
|
2097
|
+
return relevantXml;
|
|
2098
|
+
}
|
|
1433
2099
|
export {
|
|
2100
|
+
canonicalFunctionId,
|
|
2101
|
+
canonicalId,
|
|
2102
|
+
canonicalVarId,
|
|
1434
2103
|
debugPrintExpr,
|
|
1435
2104
|
parseVensimEquation,
|
|
1436
2105
|
parseVensimExpr,
|
|
1437
2106
|
parseVensimModel,
|
|
1438
2107
|
parseVensimSubscriptRange,
|
|
2108
|
+
parseXmileDimensionDef,
|
|
2109
|
+
parseXmileModel,
|
|
2110
|
+
parseXmileVariableDef,
|
|
2111
|
+
preprocessVensimModel,
|
|
1439
2112
|
prettyPrintExpr,
|
|
1440
2113
|
printExprStats,
|
|
1441
2114
|
reduceConditionals,
|