@sdeverywhere/parse 0.1.5 → 0.1.7
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 +1 -1
- package/dist/index.d.ts +368 -358
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1776 -1994
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
- package/dist/index.cjs +0 -2170
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -607
package/dist/index.cjs
DELETED
|
@@ -1,2170 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
|
|
29
|
-
// src/index.ts
|
|
30
|
-
var index_exports = {};
|
|
31
|
-
__export(index_exports, {
|
|
32
|
-
canonicalFunctionId: () => canonicalFunctionId,
|
|
33
|
-
canonicalId: () => canonicalId,
|
|
34
|
-
canonicalVarId: () => canonicalVarId,
|
|
35
|
-
debugPrintExpr: () => debugPrintExpr,
|
|
36
|
-
parseVensimEquation: () => parseVensimEquation,
|
|
37
|
-
parseVensimExpr: () => parseVensimExpr,
|
|
38
|
-
parseVensimModel: () => parseVensimModel,
|
|
39
|
-
parseVensimSubscriptRange: () => parseVensimSubscriptRange,
|
|
40
|
-
parseXmileDimensionDef: () => parseXmileDimensionDef,
|
|
41
|
-
parseXmileModel: () => parseXmileModel,
|
|
42
|
-
parseXmileVariableDef: () => parseXmileVariableDef,
|
|
43
|
-
preprocessVensimModel: () => preprocessVensimModel,
|
|
44
|
-
prettyPrintExpr: () => prettyPrintExpr,
|
|
45
|
-
printExprStats: () => printExprStats,
|
|
46
|
-
reduceConditionals: () => reduceConditionals,
|
|
47
|
-
reduceExpr: () => reduceExpr,
|
|
48
|
-
toPrettyString: () => toPrettyString
|
|
49
|
-
});
|
|
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 = /[^\p{L}\p{N}_!]/gu;
|
|
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
|
-
}
|
|
74
|
-
|
|
75
|
-
// src/ast/print-expr.ts
|
|
76
|
-
var import_assert_never = require("assert-never");
|
|
77
|
-
function debugPrintExpr(expr, indent = 0) {
|
|
78
|
-
const spaces = " ".repeat(indent * 2);
|
|
79
|
-
const log = (s) => {
|
|
80
|
-
console.log(`${spaces}${s}`);
|
|
81
|
-
};
|
|
82
|
-
switch (expr.kind) {
|
|
83
|
-
case "number":
|
|
84
|
-
log(`const: ${expr.text}`);
|
|
85
|
-
break;
|
|
86
|
-
case "string":
|
|
87
|
-
log(`string: ${expr.text}`);
|
|
88
|
-
break;
|
|
89
|
-
case "keyword":
|
|
90
|
-
log(`keyword: ${expr.text}`);
|
|
91
|
-
break;
|
|
92
|
-
case "variable-ref":
|
|
93
|
-
log(`ref: ${fullIdForVarRef(expr)}`);
|
|
94
|
-
break;
|
|
95
|
-
case "unary-op":
|
|
96
|
-
log(`unary-op: ${expr.op}`);
|
|
97
|
-
debugPrintExpr(expr.expr, indent + 1);
|
|
98
|
-
break;
|
|
99
|
-
case "binary-op":
|
|
100
|
-
log(`binary-op: ${expr.op}`);
|
|
101
|
-
debugPrintExpr(expr.lhs, indent + 1);
|
|
102
|
-
debugPrintExpr(expr.rhs, indent + 1);
|
|
103
|
-
break;
|
|
104
|
-
case "parens":
|
|
105
|
-
log("parens");
|
|
106
|
-
debugPrintExpr(expr.expr, indent + 1);
|
|
107
|
-
break;
|
|
108
|
-
case "lookup-def":
|
|
109
|
-
log(`lookup-def`);
|
|
110
|
-
break;
|
|
111
|
-
case "lookup-call":
|
|
112
|
-
log(`lookup-call: ${debugPrintExpr(expr.varRef)}`);
|
|
113
|
-
debugPrintExpr(expr.arg, indent + 1);
|
|
114
|
-
break;
|
|
115
|
-
case "function-call":
|
|
116
|
-
log(`function-call: ${expr.fnId}`);
|
|
117
|
-
expr.args.forEach((arg) => debugPrintExpr(arg, indent + 1));
|
|
118
|
-
break;
|
|
119
|
-
default:
|
|
120
|
-
(0, import_assert_never.assertNever)(expr);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function toPrettyString(expr, opts) {
|
|
124
|
-
let lparen, rparen, spaceSep, commaSep;
|
|
125
|
-
if (opts?.compact === true) {
|
|
126
|
-
lparen = "(";
|
|
127
|
-
rparen = ")";
|
|
128
|
-
spaceSep = "";
|
|
129
|
-
commaSep = ",";
|
|
130
|
-
} else {
|
|
131
|
-
lparen = "( ";
|
|
132
|
-
rparen = " )";
|
|
133
|
-
spaceSep = " ";
|
|
134
|
-
commaSep = ", ";
|
|
135
|
-
}
|
|
136
|
-
switch (expr.kind) {
|
|
137
|
-
case "number":
|
|
138
|
-
return expr.text;
|
|
139
|
-
case "string":
|
|
140
|
-
return `'${expr.text}'`;
|
|
141
|
-
case "keyword":
|
|
142
|
-
return expr.text;
|
|
143
|
-
case "variable-ref":
|
|
144
|
-
if (opts?.formatVariableRef) {
|
|
145
|
-
return opts.formatVariableRef(expr);
|
|
146
|
-
} else {
|
|
147
|
-
if (expr.subscriptRefs?.length > 0) {
|
|
148
|
-
return `${expr.varName}[${expr.subscriptRefs.map((ref) => ref.subName).join(commaSep)}]`;
|
|
149
|
-
} else {
|
|
150
|
-
return expr.varName;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
case "unary-op":
|
|
154
|
-
if (expr.op === ":NOT:") {
|
|
155
|
-
return `${expr.op} ${toPrettyString(expr.expr, opts)}`;
|
|
156
|
-
} else {
|
|
157
|
-
return `${expr.op}${toPrettyString(expr.expr, opts)}`;
|
|
158
|
-
}
|
|
159
|
-
case "binary-op": {
|
|
160
|
-
let op;
|
|
161
|
-
if (opts?.html === true) {
|
|
162
|
-
switch (expr.op) {
|
|
163
|
-
case "<":
|
|
164
|
-
op = "<";
|
|
165
|
-
break;
|
|
166
|
-
case "<=":
|
|
167
|
-
op = "<=";
|
|
168
|
-
break;
|
|
169
|
-
case ">":
|
|
170
|
-
op = ">";
|
|
171
|
-
break;
|
|
172
|
-
case ">=":
|
|
173
|
-
op = ">=";
|
|
174
|
-
break;
|
|
175
|
-
default:
|
|
176
|
-
op = expr.op;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
op = expr.op;
|
|
181
|
-
}
|
|
182
|
-
const lhs = toPrettyString(expr.lhs, opts);
|
|
183
|
-
const rhs = toPrettyString(expr.rhs, opts);
|
|
184
|
-
return `${lhs}${spaceSep}${op}${spaceSep}${rhs}`;
|
|
185
|
-
}
|
|
186
|
-
case "parens":
|
|
187
|
-
return `${lparen}${toPrettyString(expr.expr, opts)}${rparen}`;
|
|
188
|
-
case "lookup-def": {
|
|
189
|
-
const pointString = (p) => {
|
|
190
|
-
return `(${p[0]},${p[1]})`;
|
|
191
|
-
};
|
|
192
|
-
const points = expr.points.map(pointString).join(commaSep);
|
|
193
|
-
if (expr.range) {
|
|
194
|
-
const min = pointString(expr.range.min);
|
|
195
|
-
const max = pointString(expr.range.max);
|
|
196
|
-
return `${lparen}[${min}-${max}]${commaSep}${points}${rparen}`;
|
|
197
|
-
} else {
|
|
198
|
-
return `${lparen}${points}${rparen}`;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
case "lookup-call": {
|
|
202
|
-
const varRef = toPrettyString(expr.varRef, opts);
|
|
203
|
-
const arg = toPrettyString(expr.arg, opts);
|
|
204
|
-
return `${varRef}${lparen}${arg}${rparen}`;
|
|
205
|
-
}
|
|
206
|
-
case "function-call": {
|
|
207
|
-
const args = expr.args.map((arg) => toPrettyString(arg, opts));
|
|
208
|
-
return `${expr.fnName}${lparen}${args.join(commaSep)}${rparen}`;
|
|
209
|
-
}
|
|
210
|
-
default:
|
|
211
|
-
(0, import_assert_never.assertNever)(expr);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function prettyPrintExpr(expr, indent = 0) {
|
|
215
|
-
const spaces = " ".repeat(indent * 2);
|
|
216
|
-
const log = (s) => {
|
|
217
|
-
console.log(`${spaces}${s}`);
|
|
218
|
-
};
|
|
219
|
-
log(toPrettyString(expr));
|
|
220
|
-
}
|
|
221
|
-
var Stats = class {
|
|
222
|
-
constructor() {
|
|
223
|
-
this.constCount = 0;
|
|
224
|
-
this.varRefCount = 0;
|
|
225
|
-
this.unaryOpCounts = /* @__PURE__ */ new Map();
|
|
226
|
-
this.binaryOpCounts = /* @__PURE__ */ new Map();
|
|
227
|
-
this.fnCallCounts = /* @__PURE__ */ new Map();
|
|
228
|
-
this.luCallCounts = /* @__PURE__ */ new Map();
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
function increment(map, key) {
|
|
232
|
-
const count = map.get(key) || 0;
|
|
233
|
-
map.set(key, count + 1);
|
|
234
|
-
}
|
|
235
|
-
function getExprStats(expr, stats) {
|
|
236
|
-
switch (expr.kind) {
|
|
237
|
-
case "number":
|
|
238
|
-
stats.constCount++;
|
|
239
|
-
break;
|
|
240
|
-
case "string":
|
|
241
|
-
break;
|
|
242
|
-
case "keyword":
|
|
243
|
-
break;
|
|
244
|
-
case "variable-ref":
|
|
245
|
-
stats.varRefCount++;
|
|
246
|
-
break;
|
|
247
|
-
case "unary-op":
|
|
248
|
-
getExprStats(expr.expr, stats);
|
|
249
|
-
increment(stats.unaryOpCounts, expr.op);
|
|
250
|
-
break;
|
|
251
|
-
case "binary-op":
|
|
252
|
-
getExprStats(expr.lhs, stats);
|
|
253
|
-
getExprStats(expr.rhs, stats);
|
|
254
|
-
increment(stats.binaryOpCounts, expr.op);
|
|
255
|
-
break;
|
|
256
|
-
case "parens":
|
|
257
|
-
getExprStats(expr.expr, stats);
|
|
258
|
-
break;
|
|
259
|
-
case "lookup-def":
|
|
260
|
-
break;
|
|
261
|
-
case "lookup-call":
|
|
262
|
-
increment(stats.luCallCounts, fullIdForVarRef(expr.varRef));
|
|
263
|
-
getExprStats(expr.arg, stats);
|
|
264
|
-
break;
|
|
265
|
-
case "function-call":
|
|
266
|
-
increment(stats.fnCallCounts, expr.fnId);
|
|
267
|
-
expr.args.forEach((arg) => getExprStats(arg, stats));
|
|
268
|
-
break;
|
|
269
|
-
default:
|
|
270
|
-
(0, import_assert_never.assertNever)(expr);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function printExprStats(exprs) {
|
|
274
|
-
const stats = new Stats();
|
|
275
|
-
for (const expr of exprs) {
|
|
276
|
-
getExprStats(expr, stats);
|
|
277
|
-
}
|
|
278
|
-
function printCount(count, label) {
|
|
279
|
-
console.log(`${count.toString().padStart(6)} ${label}`);
|
|
280
|
-
}
|
|
281
|
-
function printCounts(map) {
|
|
282
|
-
const entries = [...map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
283
|
-
let total = 0;
|
|
284
|
-
for (const entry of entries) {
|
|
285
|
-
printCount(entry[1], entry[0]);
|
|
286
|
-
total += entry[1];
|
|
287
|
-
}
|
|
288
|
-
printCount(total, "total");
|
|
289
|
-
return total;
|
|
290
|
-
}
|
|
291
|
-
let nodeCount = 0;
|
|
292
|
-
printCount(stats.constCount, "consts");
|
|
293
|
-
nodeCount += stats.constCount;
|
|
294
|
-
printCount(stats.varRefCount, "var refs");
|
|
295
|
-
nodeCount += stats.varRefCount;
|
|
296
|
-
console.log();
|
|
297
|
-
console.log("UNARY OPS");
|
|
298
|
-
nodeCount += printCounts(stats.unaryOpCounts);
|
|
299
|
-
console.log();
|
|
300
|
-
console.log("BINARY OPS");
|
|
301
|
-
nodeCount += printCounts(stats.binaryOpCounts);
|
|
302
|
-
console.log();
|
|
303
|
-
console.log("FUNCTION CALLS");
|
|
304
|
-
nodeCount += printCounts(stats.fnCallCounts);
|
|
305
|
-
console.log();
|
|
306
|
-
console.log("LOOKUP CALLS");
|
|
307
|
-
nodeCount += printCounts(stats.luCallCounts);
|
|
308
|
-
console.log();
|
|
309
|
-
console.log("TOTAL");
|
|
310
|
-
printCount(nodeCount, "nodes");
|
|
311
|
-
}
|
|
312
|
-
function fullIdForVarRef(varRef) {
|
|
313
|
-
if (varRef.subscriptRefs?.length > 0) {
|
|
314
|
-
return `${varRef.varId}[${varRef.subscriptRefs.map((ref) => ref.subId).join(",")}]`;
|
|
315
|
-
} else {
|
|
316
|
-
return varRef.varId;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// src/ast/reduce-expr.ts
|
|
321
|
-
var import_assert_never2 = require("assert-never");
|
|
322
|
-
|
|
323
|
-
// src/ast/ast-builders.ts
|
|
324
|
-
function subRef(dimOrSubName) {
|
|
325
|
-
return {
|
|
326
|
-
subName: dimOrSubName,
|
|
327
|
-
subId: canonicalId(dimOrSubName)
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function num(value, text) {
|
|
331
|
-
return {
|
|
332
|
-
kind: "number",
|
|
333
|
-
value,
|
|
334
|
-
text: text || value.toString()
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function unaryOp(op, expr) {
|
|
338
|
-
return {
|
|
339
|
-
kind: "unary-op",
|
|
340
|
-
op,
|
|
341
|
-
expr
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
function binaryOp(lhs, op, rhs) {
|
|
345
|
-
return {
|
|
346
|
-
kind: "binary-op",
|
|
347
|
-
lhs,
|
|
348
|
-
op,
|
|
349
|
-
rhs
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function parens(expr) {
|
|
353
|
-
return {
|
|
354
|
-
kind: "parens",
|
|
355
|
-
expr
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
function lookupDef(points, range) {
|
|
359
|
-
return {
|
|
360
|
-
kind: "lookup-def",
|
|
361
|
-
range,
|
|
362
|
-
points
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
function lookupCall(varRef, arg) {
|
|
366
|
-
return {
|
|
367
|
-
kind: "lookup-call",
|
|
368
|
-
varRef,
|
|
369
|
-
arg
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
function call(fnName, ...args) {
|
|
373
|
-
return {
|
|
374
|
-
kind: "function-call",
|
|
375
|
-
fnName,
|
|
376
|
-
fnId: canonicalFunctionId(fnName),
|
|
377
|
-
args
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// src/ast/reduce-expr.ts
|
|
382
|
-
function reduceExpr(expr, opts) {
|
|
383
|
-
switch (expr.kind) {
|
|
384
|
-
case "number":
|
|
385
|
-
case "string":
|
|
386
|
-
case "keyword":
|
|
387
|
-
return expr;
|
|
388
|
-
case "variable-ref":
|
|
389
|
-
if (opts?.resolveVarRef !== void 0) {
|
|
390
|
-
const resolvedExpr = opts.resolveVarRef(expr);
|
|
391
|
-
if (resolvedExpr) {
|
|
392
|
-
return resolvedExpr;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return expr;
|
|
396
|
-
case "unary-op": {
|
|
397
|
-
const child = reduceExpr(expr.expr, opts);
|
|
398
|
-
switch (expr.op) {
|
|
399
|
-
case "+":
|
|
400
|
-
return child;
|
|
401
|
-
case "-":
|
|
402
|
-
if (child.kind === "number") {
|
|
403
|
-
return num(-child.value);
|
|
404
|
-
} else {
|
|
405
|
-
return unaryOp("-", child);
|
|
406
|
-
}
|
|
407
|
-
case ":NOT:":
|
|
408
|
-
if (child.kind === "number") {
|
|
409
|
-
return num(child.value === 0 ? 1 : 0);
|
|
410
|
-
} else {
|
|
411
|
-
return unaryOp(":NOT:", child);
|
|
412
|
-
}
|
|
413
|
-
default:
|
|
414
|
-
(0, import_assert_never2.assertNever)(expr);
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
case "binary-op": {
|
|
419
|
-
const lhs = reduceExpr(expr.lhs, opts);
|
|
420
|
-
const rhs = reduceExpr(expr.rhs, opts);
|
|
421
|
-
if (lhs.kind === "number" && rhs.kind === "number") {
|
|
422
|
-
switch (expr.op) {
|
|
423
|
-
case "+":
|
|
424
|
-
return num(lhs.value + rhs.value);
|
|
425
|
-
case "-":
|
|
426
|
-
return num(lhs.value - rhs.value);
|
|
427
|
-
case "*":
|
|
428
|
-
return num(lhs.value * rhs.value);
|
|
429
|
-
case "/":
|
|
430
|
-
return num(lhs.value / rhs.value);
|
|
431
|
-
case "^":
|
|
432
|
-
return num(Math.pow(lhs.value, rhs.value));
|
|
433
|
-
case "=":
|
|
434
|
-
return num(lhs.value === rhs.value ? 1 : 0);
|
|
435
|
-
case "<>":
|
|
436
|
-
return num(lhs.value !== rhs.value ? 1 : 0);
|
|
437
|
-
case "<":
|
|
438
|
-
return num(lhs.value < rhs.value ? 1 : 0);
|
|
439
|
-
case ">":
|
|
440
|
-
return num(lhs.value > rhs.value ? 1 : 0);
|
|
441
|
-
case "<=":
|
|
442
|
-
return num(lhs.value <= rhs.value ? 1 : 0);
|
|
443
|
-
case ">=":
|
|
444
|
-
return num(lhs.value >= rhs.value ? 1 : 0);
|
|
445
|
-
case ":AND:":
|
|
446
|
-
return num(lhs.value !== 0 && rhs.value !== 0 ? 1 : 0);
|
|
447
|
-
case ":OR:":
|
|
448
|
-
return num(lhs.value !== 0 || rhs.value !== 0 ? 1 : 0);
|
|
449
|
-
default:
|
|
450
|
-
(0, import_assert_never2.assertNever)(expr);
|
|
451
|
-
}
|
|
452
|
-
} else if (lhs.kind === "number" || rhs.kind === "number") {
|
|
453
|
-
const lhsNum = lhs.kind === "number" ? lhs.value : void 0;
|
|
454
|
-
const rhsNum = rhs.kind === "number" ? rhs.value : void 0;
|
|
455
|
-
const numValue = lhsNum !== void 0 ? lhsNum : rhsNum;
|
|
456
|
-
const otherSide = lhsNum !== void 0 ? rhs : lhs;
|
|
457
|
-
switch (expr.op) {
|
|
458
|
-
case "+": {
|
|
459
|
-
if (numValue === 0) {
|
|
460
|
-
return otherSide;
|
|
461
|
-
} else if (otherSide.kind === "binary-op" && otherSide.op === "+" && (otherSide.lhs.kind === "number" || otherSide.rhs.kind === "number")) {
|
|
462
|
-
const otherSideLhsNum = otherSide.lhs.kind === "number" ? otherSide.lhs.value : void 0;
|
|
463
|
-
const otherSideRhsNum = otherSide.rhs.kind === "number" ? otherSide.rhs.value : void 0;
|
|
464
|
-
const otherSideConstValue = otherSideLhsNum !== void 0 ? otherSideLhsNum : otherSideRhsNum;
|
|
465
|
-
const otherSideOtherPart = otherSideLhsNum !== void 0 ? otherSide.rhs : otherSide.lhs;
|
|
466
|
-
return binaryOp(num(numValue + otherSideConstValue), "+", otherSideOtherPart);
|
|
467
|
-
}
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
case "-": {
|
|
471
|
-
if (rhsNum === 0) {
|
|
472
|
-
return lhs;
|
|
473
|
-
} else if (lhsNum === 0) {
|
|
474
|
-
return unaryOp("-", rhs);
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case "*": {
|
|
479
|
-
if (numValue === 0) {
|
|
480
|
-
return num(0);
|
|
481
|
-
} else if (numValue === 1) {
|
|
482
|
-
return otherSide;
|
|
483
|
-
} else if (otherSide.kind === "binary-op" && otherSide.op === "*" && (otherSide.lhs.kind === "number" || otherSide.rhs.kind === "number")) {
|
|
484
|
-
const otherSideLhsNum = otherSide.lhs.kind === "number" ? otherSide.lhs.value : void 0;
|
|
485
|
-
const otherSideRhsNum = otherSide.rhs.kind === "number" ? otherSide.rhs.value : void 0;
|
|
486
|
-
const otherSideConstValue = otherSideLhsNum !== void 0 ? otherSideLhsNum : otherSideRhsNum;
|
|
487
|
-
const otherSideOtherPart = otherSideLhsNum !== void 0 ? otherSide.rhs : otherSide.lhs;
|
|
488
|
-
return binaryOp(num(numValue * otherSideConstValue), "*", otherSideOtherPart);
|
|
489
|
-
}
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
case "/": {
|
|
493
|
-
if (rhsNum === 1) {
|
|
494
|
-
return lhs;
|
|
495
|
-
}
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
case "^": {
|
|
499
|
-
if (rhsNum === 0) {
|
|
500
|
-
return num(1);
|
|
501
|
-
} else if (rhsNum === 1) {
|
|
502
|
-
return lhs;
|
|
503
|
-
}
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
case ":AND:":
|
|
507
|
-
return numValue === 0 ? num(0) : otherSide;
|
|
508
|
-
case ":OR:":
|
|
509
|
-
return numValue !== 0 ? num(1) : otherSide;
|
|
510
|
-
default:
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return {
|
|
515
|
-
kind: "binary-op",
|
|
516
|
-
lhs,
|
|
517
|
-
op: expr.op,
|
|
518
|
-
rhs
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
case "parens": {
|
|
522
|
-
const child = reduceExpr(expr.expr, opts);
|
|
523
|
-
return applyParens(child);
|
|
524
|
-
}
|
|
525
|
-
case "lookup-def":
|
|
526
|
-
return expr;
|
|
527
|
-
case "lookup-call":
|
|
528
|
-
return expr;
|
|
529
|
-
case "function-call": {
|
|
530
|
-
if (expr.fnId === "_IF_THEN_ELSE") {
|
|
531
|
-
const conditionExpr = reduceExpr(expr.args[0], opts);
|
|
532
|
-
if (conditionExpr.kind === "number") {
|
|
533
|
-
const branchExpr = conditionExpr.value !== 0 ? reduceExpr(expr.args[1], opts) : reduceExpr(expr.args[2], opts);
|
|
534
|
-
return applyParens(branchExpr);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const reducedArgs = expr.args.map((arg) => reduceExpr(arg, opts));
|
|
538
|
-
const allConst = reducedArgs.every((arg) => arg.kind === "number");
|
|
539
|
-
if (allConst) {
|
|
540
|
-
const constArg = (index) => {
|
|
541
|
-
const num2 = reducedArgs[index];
|
|
542
|
-
return num2.value;
|
|
543
|
-
};
|
|
544
|
-
switch (expr.fnId) {
|
|
545
|
-
case "_ABS":
|
|
546
|
-
return num(Math.abs(constArg(0)));
|
|
547
|
-
case "_COS":
|
|
548
|
-
return num(Math.cos(constArg(0)));
|
|
549
|
-
case "_EXP":
|
|
550
|
-
return num(Math.exp(constArg(0)));
|
|
551
|
-
case "_INITIAL":
|
|
552
|
-
return reducedArgs[0];
|
|
553
|
-
case "_INTEGER":
|
|
554
|
-
return num(Math.trunc(constArg(0)));
|
|
555
|
-
case "_LN":
|
|
556
|
-
return num(Math.log(constArg(0)));
|
|
557
|
-
case "_MAX":
|
|
558
|
-
return num(Math.max(constArg(0), constArg(1)));
|
|
559
|
-
case "_MIN":
|
|
560
|
-
return num(Math.min(constArg(0), constArg(1)));
|
|
561
|
-
case "_MODULO":
|
|
562
|
-
return num(constArg(0) % constArg(1));
|
|
563
|
-
case "_POWER":
|
|
564
|
-
return num(Math.pow(constArg(0), constArg(1)));
|
|
565
|
-
case "_SIN":
|
|
566
|
-
return num(Math.sin(constArg(0)));
|
|
567
|
-
case "_SQRT":
|
|
568
|
-
return num(Math.sqrt(constArg(0)));
|
|
569
|
-
default:
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
kind: "function-call",
|
|
575
|
-
fnName: expr.fnName,
|
|
576
|
-
fnId: expr.fnId,
|
|
577
|
-
args: reducedArgs
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
default:
|
|
581
|
-
(0, import_assert_never2.assertNever)(expr);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function reduceConditionals(expr, opts) {
|
|
585
|
-
switch (expr.kind) {
|
|
586
|
-
case "number":
|
|
587
|
-
case "string":
|
|
588
|
-
case "keyword":
|
|
589
|
-
return expr;
|
|
590
|
-
case "variable-ref":
|
|
591
|
-
return expr;
|
|
592
|
-
case "unary-op": {
|
|
593
|
-
const child = reduceConditionals(expr.expr, opts);
|
|
594
|
-
return unaryOp(expr.op, child);
|
|
595
|
-
}
|
|
596
|
-
case "binary-op": {
|
|
597
|
-
const lhs = reduceConditionals(expr.lhs, opts);
|
|
598
|
-
const rhs = reduceConditionals(expr.rhs, opts);
|
|
599
|
-
return binaryOp(lhs, expr.op, rhs);
|
|
600
|
-
}
|
|
601
|
-
case "parens": {
|
|
602
|
-
const child = reduceConditionals(expr.expr, opts);
|
|
603
|
-
return applyParens(child);
|
|
604
|
-
}
|
|
605
|
-
case "lookup-def":
|
|
606
|
-
return expr;
|
|
607
|
-
case "lookup-call": {
|
|
608
|
-
const arg = reduceConditionals(expr.arg, opts);
|
|
609
|
-
return lookupCall(expr.varRef, arg);
|
|
610
|
-
}
|
|
611
|
-
case "function-call": {
|
|
612
|
-
if (expr.fnId === "_IF_THEN_ELSE") {
|
|
613
|
-
const conditionExpr = reduceExpr(expr.args[0], opts);
|
|
614
|
-
if (conditionExpr.kind === "number") {
|
|
615
|
-
const branchExpr = conditionExpr.value !== 0 ? reduceConditionals(expr.args[1], opts) : reduceConditionals(expr.args[2], opts);
|
|
616
|
-
return applyParens(branchExpr);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
const reducedArgs = expr.args.map((arg) => reduceConditionals(arg, opts));
|
|
620
|
-
return {
|
|
621
|
-
kind: "function-call",
|
|
622
|
-
fnName: expr.fnName,
|
|
623
|
-
fnId: expr.fnId,
|
|
624
|
-
args: reducedArgs
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
default:
|
|
628
|
-
(0, import_assert_never2.assertNever)(expr);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
function applyParens(child) {
|
|
632
|
-
switch (child.kind) {
|
|
633
|
-
case "number":
|
|
634
|
-
case "string":
|
|
635
|
-
case "keyword":
|
|
636
|
-
case "variable-ref":
|
|
637
|
-
return child;
|
|
638
|
-
default:
|
|
639
|
-
return parens(child);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// src/vensim/impl/subscript-range-reader.js
|
|
644
|
-
var import_antlr4_vensim2 = require("antlr4-vensim");
|
|
645
|
-
|
|
646
|
-
// src/vensim/impl/antlr-parser.js
|
|
647
|
-
var import_antlr4 = __toESM(require("antlr4"), 1);
|
|
648
|
-
var import_antlr4_vensim = require("antlr4-vensim");
|
|
649
|
-
function createAntlrParser(input) {
|
|
650
|
-
const errorListener = new CustomErrorListener(input);
|
|
651
|
-
let chars = new import_antlr4.default.InputStream(input);
|
|
652
|
-
let lexer = new import_antlr4_vensim.ModelLexer(chars);
|
|
653
|
-
lexer.removeErrorListeners();
|
|
654
|
-
lexer.addErrorListener(errorListener);
|
|
655
|
-
let tokens = new import_antlr4.default.CommonTokenStream(lexer);
|
|
656
|
-
let parser = new import_antlr4_vensim.ModelParser(tokens);
|
|
657
|
-
parser.buildParseTrees = true;
|
|
658
|
-
parser.removeErrorListeners();
|
|
659
|
-
parser.addErrorListener(errorListener);
|
|
660
|
-
return parser;
|
|
661
|
-
}
|
|
662
|
-
var CustomErrorListener = class extends import_antlr4.default.error.ErrorListener {
|
|
663
|
-
constructor(input) {
|
|
664
|
-
super();
|
|
665
|
-
this.input = input;
|
|
666
|
-
}
|
|
667
|
-
syntaxError(_recognizer, _offendingSymbol, line, column, msg) {
|
|
668
|
-
throw new Error(msg, {
|
|
669
|
-
cause: {
|
|
670
|
-
code: "VensimParseError",
|
|
671
|
-
line,
|
|
672
|
-
column
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
// src/vensim/impl/subscript-range-reader.js
|
|
679
|
-
var SubscriptRangeReader = class extends import_antlr4_vensim2.ModelVisitor {
|
|
680
|
-
/**
|
|
681
|
-
* @public
|
|
682
|
-
* @param {import('../vensim-parse-context').VensimParseContext} parseContext An object
|
|
683
|
-
* that provides access to file system resources (such as external data files) that are
|
|
684
|
-
* referenced during the parse phase.
|
|
685
|
-
*/
|
|
686
|
-
constructor(parseContext) {
|
|
687
|
-
super();
|
|
688
|
-
this.parseContext = parseContext;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Parse the given Vensim subscript range definition and return a `DimensionDef` AST node.
|
|
692
|
-
*
|
|
693
|
-
* @public
|
|
694
|
-
* @param {string} subscriptRangeText A string containing the Vensim subscript range definition.
|
|
695
|
-
* @returns {import('../../ast/ast-types').DimensionDef} A `DimensionDef` AST node.
|
|
696
|
-
*/
|
|
697
|
-
/*public*/
|
|
698
|
-
parse(subscriptRangeText) {
|
|
699
|
-
const parser = createAntlrParser(subscriptRangeText);
|
|
700
|
-
const subscriptRangeCtx = parser.subscriptRange();
|
|
701
|
-
return this.visitSubscriptRange(subscriptRangeCtx);
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Process the given ANTLR `SubscriptRangeContext` from an already parsed Vensim
|
|
705
|
-
* subscript range definition.
|
|
706
|
-
*
|
|
707
|
-
* @public
|
|
708
|
-
* @param {import('antlr4-vensim').SubscriptRangeContext} ctx The ANTLR `SubscriptRangeContext`.
|
|
709
|
-
* @returns {import('../../ast/ast-types').Expr} A `SubscriptRange` AST node.
|
|
710
|
-
*/
|
|
711
|
-
/*public*/
|
|
712
|
-
visitSubscriptRange(ctx) {
|
|
713
|
-
this.subscriptNames = [];
|
|
714
|
-
this.subscriptMappings = [];
|
|
715
|
-
const comment = "";
|
|
716
|
-
const ids = ctx.Id();
|
|
717
|
-
if (ids.length === 1) {
|
|
718
|
-
const dimName = ids[0].getText();
|
|
719
|
-
const dimId = canonicalId(dimName);
|
|
720
|
-
super.visitSubscriptRange(ctx);
|
|
721
|
-
return {
|
|
722
|
-
dimName,
|
|
723
|
-
dimId,
|
|
724
|
-
familyName: dimName,
|
|
725
|
-
familyId: dimId,
|
|
726
|
-
subscriptRefs: this.subscriptNames.map((subName) => {
|
|
727
|
-
return {
|
|
728
|
-
subName,
|
|
729
|
-
subId: canonicalId(subName)
|
|
730
|
-
};
|
|
731
|
-
}),
|
|
732
|
-
subscriptMappings: this.subscriptMappings,
|
|
733
|
-
comment
|
|
734
|
-
};
|
|
735
|
-
} else if (ids.length === 2) {
|
|
736
|
-
const dimName = ids[0].getText();
|
|
737
|
-
const dimId = canonicalId(dimName);
|
|
738
|
-
const familyName = ids[1].getText();
|
|
739
|
-
const familyId = canonicalId(familyName);
|
|
740
|
-
return {
|
|
741
|
-
dimName,
|
|
742
|
-
dimId,
|
|
743
|
-
familyName,
|
|
744
|
-
familyId,
|
|
745
|
-
subscriptRefs: [],
|
|
746
|
-
subscriptMappings: [],
|
|
747
|
-
comment
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
visitSubscriptDefList(ctx) {
|
|
752
|
-
for (const subscriptDef of ctx.children) {
|
|
753
|
-
if (subscriptDef.symbol?.type === import_antlr4_vensim2.ModelParser.Id) {
|
|
754
|
-
this.subscriptNames.push(subscriptDef.getText());
|
|
755
|
-
} else if (subscriptDef.ruleIndex === import_antlr4_vensim2.ModelParser.RULE_subscriptSequence) {
|
|
756
|
-
this.visitSubscriptSequence(subscriptDef);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
visitSubscriptSequence(ctx) {
|
|
761
|
-
const re = /^(.*?)(\d+)$/;
|
|
762
|
-
const ids = ctx.Id().map((id) => id.getText());
|
|
763
|
-
const matches = ids.map((id) => re.exec(id));
|
|
764
|
-
if (matches[0][1] === matches[1][1]) {
|
|
765
|
-
const prefix = matches[0][1];
|
|
766
|
-
const start = parseInt(matches[0][2]);
|
|
767
|
-
const end = parseInt(matches[1][2]);
|
|
768
|
-
for (let i = start; i <= end; i++) {
|
|
769
|
-
this.subscriptNames.push(prefix + i);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
visitSubscriptMapping(ctx) {
|
|
774
|
-
const toDimName = ctx.Id().getText();
|
|
775
|
-
this.mappedSubscriptNames = [];
|
|
776
|
-
super.visitSubscriptMapping(ctx);
|
|
777
|
-
this.subscriptMappings.push({
|
|
778
|
-
toDimName,
|
|
779
|
-
toDimId: canonicalId(toDimName),
|
|
780
|
-
subscriptRefs: this.mappedSubscriptNames.map((subName) => {
|
|
781
|
-
return {
|
|
782
|
-
subName,
|
|
783
|
-
subId: canonicalId(subName)
|
|
784
|
-
};
|
|
785
|
-
})
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
visitSubscriptList(ctx) {
|
|
789
|
-
this.mappedSubscriptNames = ctx.Id().map((id) => id.getText());
|
|
790
|
-
}
|
|
791
|
-
visitCall(ctx) {
|
|
792
|
-
const fnName = ctx.Id().getText();
|
|
793
|
-
const fnId = canonicalFunctionId(fnName);
|
|
794
|
-
if (fnId === "_GET_DIRECT_SUBSCRIPT") {
|
|
795
|
-
super.visitCall(ctx);
|
|
796
|
-
} else {
|
|
797
|
-
throw new Error(
|
|
798
|
-
`Only 'GET DIRECT SUBSCRIPT' calls are supported in subscript range definitions, but saw '${fnName}'`
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
visitExprList(ctx) {
|
|
803
|
-
const args = ctx.expr().map((expr) => {
|
|
804
|
-
const exprText = expr.getText();
|
|
805
|
-
return exprText.replaceAll("'", "");
|
|
806
|
-
});
|
|
807
|
-
const fileName = args[0];
|
|
808
|
-
const tabOrDelimiter = args[1];
|
|
809
|
-
const firstCell = args[2];
|
|
810
|
-
const lastCell = args[3];
|
|
811
|
-
const prefix = args[4];
|
|
812
|
-
this.subscriptNames = this.parseContext?.getDirectSubscripts(fileName, tabOrDelimiter, firstCell, lastCell, prefix) || [];
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
// src/vensim/parse-vensim-subscript-range.ts
|
|
817
|
-
function parseVensimSubscriptRange(input, context) {
|
|
818
|
-
const subscriptReader = new SubscriptRangeReader(context);
|
|
819
|
-
return subscriptReader.parse(input);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// src/vensim/impl/expr-reader.js
|
|
823
|
-
var import_antlr4_vensim3 = require("antlr4-vensim");
|
|
824
|
-
var ExprReader = class extends import_antlr4_vensim3.ModelVisitor {
|
|
825
|
-
constructor() {
|
|
826
|
-
super();
|
|
827
|
-
this.callStack = [];
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Parse the given Vensim expression definition and return an `Expr` AST node.
|
|
831
|
-
*
|
|
832
|
-
* @public
|
|
833
|
-
* @param {string} exprText A string containing the Vensim expression.
|
|
834
|
-
* @returns {import('../../ast/ast-types').Expr} An `Expr` AST node.
|
|
835
|
-
*/
|
|
836
|
-
/*public*/
|
|
837
|
-
parse(exprText) {
|
|
838
|
-
const parser = createAntlrParser(exprText);
|
|
839
|
-
const exprCtx = parser.expr();
|
|
840
|
-
return this.visitExpr(exprCtx);
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Process the given ANTLR `ExprContext` from an already parsed Vensim
|
|
844
|
-
* expression definition.
|
|
845
|
-
*
|
|
846
|
-
* @public
|
|
847
|
-
* @param {import('antlr4-vensim').ExprContext} ctx The ANTLR `ExprContext`.
|
|
848
|
-
* @returns {import('../../ast/ast-types').Expr} An `Expr` AST node.
|
|
849
|
-
*/
|
|
850
|
-
/*public*/
|
|
851
|
-
visitExpr(ctx) {
|
|
852
|
-
ctx.accept(this);
|
|
853
|
-
return this.expr;
|
|
854
|
-
}
|
|
855
|
-
//
|
|
856
|
-
// Constants
|
|
857
|
-
//
|
|
858
|
-
visitConst(ctx) {
|
|
859
|
-
const text = ctx.Const().getText();
|
|
860
|
-
if (text.startsWith("'") && text.endsWith("'")) {
|
|
861
|
-
this.expr = {
|
|
862
|
-
kind: "string",
|
|
863
|
-
text: text.substr(1, text.length - 2)
|
|
864
|
-
};
|
|
865
|
-
} else {
|
|
866
|
-
const value = parseFloat(text);
|
|
867
|
-
this.expr = {
|
|
868
|
-
kind: "number",
|
|
869
|
-
value,
|
|
870
|
-
text
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
//
|
|
875
|
-
// Keywords
|
|
876
|
-
//
|
|
877
|
-
visitKeyword(ctx) {
|
|
878
|
-
const text = ctx.Keyword().getText();
|
|
879
|
-
this.expr = {
|
|
880
|
-
kind: "keyword",
|
|
881
|
-
text
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
//
|
|
885
|
-
// Function calls and variables
|
|
886
|
-
//
|
|
887
|
-
visitCall(ctx) {
|
|
888
|
-
const vensimFnName = ctx.Id().getText();
|
|
889
|
-
const fnId = canonicalFunctionId(vensimFnName);
|
|
890
|
-
this.callStack.push({ fn: fnId, args: [] });
|
|
891
|
-
super.visitCall(ctx);
|
|
892
|
-
const callInfo = this.callStack.pop();
|
|
893
|
-
this.expr = {
|
|
894
|
-
kind: "function-call",
|
|
895
|
-
fnName: vensimFnName,
|
|
896
|
-
fnId,
|
|
897
|
-
args: callInfo.args
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
visitExprList(ctx) {
|
|
901
|
-
const exprs = ctx.expr();
|
|
902
|
-
for (let i = 0; i < exprs.length; i++) {
|
|
903
|
-
exprs[i].accept(this);
|
|
904
|
-
const n = this.callStack.length;
|
|
905
|
-
if (n > 0) {
|
|
906
|
-
this.callStack[n - 1].args.push(this.expr);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
visitVar(ctx) {
|
|
911
|
-
const vensimVarName = ctx.Id().getText().trim();
|
|
912
|
-
const varId = canonicalId(vensimVarName);
|
|
913
|
-
this.subscripts = void 0;
|
|
914
|
-
super.visitVar(ctx);
|
|
915
|
-
const subscriptNames = this.subscripts;
|
|
916
|
-
const subscriptRefs = subscriptNames?.map((name) => {
|
|
917
|
-
return {
|
|
918
|
-
subName: name,
|
|
919
|
-
subId: canonicalId(name)
|
|
920
|
-
};
|
|
921
|
-
});
|
|
922
|
-
this.subscripts = void 0;
|
|
923
|
-
this.expr = {
|
|
924
|
-
kind: "variable-ref",
|
|
925
|
-
varName: vensimVarName,
|
|
926
|
-
varId,
|
|
927
|
-
subscriptRefs
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
visitSubscriptList(ctx) {
|
|
931
|
-
this.subscripts = ctx.Id().map((id) => id.getText());
|
|
932
|
-
}
|
|
933
|
-
//
|
|
934
|
-
// Lookups
|
|
935
|
-
//
|
|
936
|
-
getPoint(lookupPoint) {
|
|
937
|
-
const exprs = lookupPoint.expr();
|
|
938
|
-
if (exprs.length >= 2) {
|
|
939
|
-
return [parseFloat(exprs[0].getText()), parseFloat(exprs[1].getText())];
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
visitLookupRange(ctx) {
|
|
943
|
-
this.lookupRange = ctx.lookupPoint().map((p) => this.getPoint(p));
|
|
944
|
-
super.visitLookupRange(ctx);
|
|
945
|
-
}
|
|
946
|
-
visitLookupPointList(ctx) {
|
|
947
|
-
this.lookupPoints = ctx.lookupPoint().map((p) => this.getPoint(p));
|
|
948
|
-
super.visitLookupPointList(ctx);
|
|
949
|
-
}
|
|
950
|
-
visitLookupArg(ctx) {
|
|
951
|
-
super.visitLookupArg(ctx);
|
|
952
|
-
let range;
|
|
953
|
-
if (this.lookupRange && this.lookupRange.length === 2) {
|
|
954
|
-
range = {
|
|
955
|
-
min: this.lookupRange[0],
|
|
956
|
-
max: this.lookupRange[1]
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
this.expr = {
|
|
960
|
-
kind: "lookup-def",
|
|
961
|
-
range,
|
|
962
|
-
points: this.lookupPoints
|
|
963
|
-
};
|
|
964
|
-
this.lookupRange = void 0;
|
|
965
|
-
this.lookupPoints = void 0;
|
|
966
|
-
}
|
|
967
|
-
visitLookupCall(ctx) {
|
|
968
|
-
const lookupVarName = ctx.Id().getText();
|
|
969
|
-
const lookupVarId = canonicalId(lookupVarName);
|
|
970
|
-
if (ctx.subscriptList()) {
|
|
971
|
-
ctx.subscriptList().accept(this);
|
|
972
|
-
}
|
|
973
|
-
const subscriptNames = this.subscripts;
|
|
974
|
-
const subscriptRefs = subscriptNames?.map((name) => {
|
|
975
|
-
return {
|
|
976
|
-
subName: name,
|
|
977
|
-
subId: canonicalId(name)
|
|
978
|
-
};
|
|
979
|
-
});
|
|
980
|
-
this.subscripts = void 0;
|
|
981
|
-
const lookupVarRef = {
|
|
982
|
-
kind: "variable-ref",
|
|
983
|
-
varName: lookupVarName,
|
|
984
|
-
varId: lookupVarId,
|
|
985
|
-
subscriptRefs
|
|
986
|
-
};
|
|
987
|
-
ctx.expr().accept(this);
|
|
988
|
-
const lookupArg = this.expr;
|
|
989
|
-
this.expr = {
|
|
990
|
-
kind: "lookup-call",
|
|
991
|
-
varRef: lookupVarRef,
|
|
992
|
-
arg: lookupArg
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
//
|
|
996
|
-
// Unary operators
|
|
997
|
-
//
|
|
998
|
-
completeUnary(op) {
|
|
999
|
-
const child = this.expr;
|
|
1000
|
-
this.expr = {
|
|
1001
|
-
kind: "unary-op",
|
|
1002
|
-
op,
|
|
1003
|
-
expr: child
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
visitNegative(ctx) {
|
|
1007
|
-
super.visitNegative(ctx);
|
|
1008
|
-
this.completeUnary("-");
|
|
1009
|
-
}
|
|
1010
|
-
visitPositive(ctx) {
|
|
1011
|
-
super.visitPositive(ctx);
|
|
1012
|
-
this.completeUnary("+");
|
|
1013
|
-
}
|
|
1014
|
-
visitNot(ctx) {
|
|
1015
|
-
super.visitNot(ctx);
|
|
1016
|
-
this.completeUnary(":NOT:");
|
|
1017
|
-
}
|
|
1018
|
-
//
|
|
1019
|
-
// Binary operators
|
|
1020
|
-
//
|
|
1021
|
-
visitBinaryArgs(ctx, op) {
|
|
1022
|
-
ctx.expr(0).accept(this);
|
|
1023
|
-
const lhs = this.expr;
|
|
1024
|
-
ctx.expr(1).accept(this);
|
|
1025
|
-
const rhs = this.expr;
|
|
1026
|
-
this.expr = {
|
|
1027
|
-
kind: "binary-op",
|
|
1028
|
-
lhs,
|
|
1029
|
-
op,
|
|
1030
|
-
rhs
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
visitPower(ctx) {
|
|
1034
|
-
this.visitBinaryArgs(ctx, "^");
|
|
1035
|
-
}
|
|
1036
|
-
visitMulDiv(ctx) {
|
|
1037
|
-
this.visitBinaryArgs(ctx, ctx.op.type === import_antlr4_vensim3.ModelLexer.Star ? "*" : "/");
|
|
1038
|
-
}
|
|
1039
|
-
visitAddSub(ctx) {
|
|
1040
|
-
this.visitBinaryArgs(ctx, ctx.op.type === import_antlr4_vensim3.ModelLexer.Plus ? "+" : "-");
|
|
1041
|
-
}
|
|
1042
|
-
visitRelational(ctx) {
|
|
1043
|
-
let op;
|
|
1044
|
-
switch (ctx.op.type) {
|
|
1045
|
-
case import_antlr4_vensim3.ModelLexer.Less:
|
|
1046
|
-
op = "<";
|
|
1047
|
-
break;
|
|
1048
|
-
case import_antlr4_vensim3.ModelLexer.Greater:
|
|
1049
|
-
op = ">";
|
|
1050
|
-
break;
|
|
1051
|
-
case import_antlr4_vensim3.ModelLexer.LessEqual:
|
|
1052
|
-
op = "<=";
|
|
1053
|
-
break;
|
|
1054
|
-
case import_antlr4_vensim3.ModelLexer.GreaterEqual:
|
|
1055
|
-
op = ">=";
|
|
1056
|
-
break;
|
|
1057
|
-
default:
|
|
1058
|
-
throw new Error(`Unexpected relational operator '${op}'`);
|
|
1059
|
-
}
|
|
1060
|
-
this.visitBinaryArgs(ctx, op);
|
|
1061
|
-
}
|
|
1062
|
-
visitEquality(ctx) {
|
|
1063
|
-
this.visitBinaryArgs(ctx, ctx.op.type === import_antlr4_vensim3.ModelLexer.Equal ? "=" : "<>");
|
|
1064
|
-
}
|
|
1065
|
-
visitAnd(ctx) {
|
|
1066
|
-
this.visitBinaryArgs(ctx, ":AND:");
|
|
1067
|
-
}
|
|
1068
|
-
visitOr(ctx) {
|
|
1069
|
-
this.visitBinaryArgs(ctx, ":OR:");
|
|
1070
|
-
}
|
|
1071
|
-
//
|
|
1072
|
-
// Tokens
|
|
1073
|
-
//
|
|
1074
|
-
visitParens(ctx) {
|
|
1075
|
-
super.visitParens(ctx);
|
|
1076
|
-
const child = this.expr;
|
|
1077
|
-
this.expr = {
|
|
1078
|
-
kind: "parens",
|
|
1079
|
-
expr: child
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
// src/vensim/parse-vensim-expr.ts
|
|
1085
|
-
function parseVensimExpr(input) {
|
|
1086
|
-
const exprReader = new ExprReader();
|
|
1087
|
-
return exprReader.parse(input);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// src/vensim/impl/equation-reader.js
|
|
1091
|
-
var import_antlr4_vensim4 = require("antlr4-vensim");
|
|
1092
|
-
var EquationReader = class extends import_antlr4_vensim4.ModelVisitor {
|
|
1093
|
-
constructor() {
|
|
1094
|
-
super();
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Parse the given Vensim equation definition and return an `Equation` AST node.
|
|
1098
|
-
*
|
|
1099
|
-
* @public
|
|
1100
|
-
* @param {string} equationText A string containing the Vensim equation definition.
|
|
1101
|
-
* @return {import('../../ast/ast-types').Equation} An `Equation` AST node.
|
|
1102
|
-
*/
|
|
1103
|
-
/*public*/
|
|
1104
|
-
parse(equationText) {
|
|
1105
|
-
const parser = createAntlrParser(equationText);
|
|
1106
|
-
const equationCtx = parser.equation();
|
|
1107
|
-
return this.visitEquation(equationCtx);
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Process the given ANTLR `EquationContext` from an already parsed Vensim
|
|
1111
|
-
* equation definition.
|
|
1112
|
-
*
|
|
1113
|
-
* @public
|
|
1114
|
-
* @param {import('antlr4-vensim').EquationContext} ctx The ANTLR `EquationContext`.
|
|
1115
|
-
* @returns {import('../../ast/ast-types').Equation} An `Equation` AST node.
|
|
1116
|
-
*/
|
|
1117
|
-
/*public*/
|
|
1118
|
-
visitEquation(ctx) {
|
|
1119
|
-
this.equationLhs = void 0;
|
|
1120
|
-
this.lookupDef = void 0;
|
|
1121
|
-
ctx.lhs().accept(this);
|
|
1122
|
-
let equationRhs;
|
|
1123
|
-
const exprCtx = ctx.expr();
|
|
1124
|
-
if (exprCtx) {
|
|
1125
|
-
const exprReader = new ExprReader();
|
|
1126
|
-
const expr = exprReader.visitExpr(exprCtx);
|
|
1127
|
-
equationRhs = {
|
|
1128
|
-
kind: "expr",
|
|
1129
|
-
expr
|
|
1130
|
-
};
|
|
1131
|
-
} else if (ctx.constList()) {
|
|
1132
|
-
ctx.constList().accept(this);
|
|
1133
|
-
equationRhs = {
|
|
1134
|
-
kind: "const-list",
|
|
1135
|
-
constants: this.constants,
|
|
1136
|
-
text: this.constListText
|
|
1137
|
-
};
|
|
1138
|
-
} else if (ctx.lookup()) {
|
|
1139
|
-
ctx.lookup().accept(this);
|
|
1140
|
-
equationRhs = {
|
|
1141
|
-
kind: "lookup",
|
|
1142
|
-
lookupDef: this.lookupDef
|
|
1143
|
-
};
|
|
1144
|
-
} else {
|
|
1145
|
-
equationRhs = {
|
|
1146
|
-
kind: "data"
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
if (this.equationLhs) {
|
|
1150
|
-
this.equation = {
|
|
1151
|
-
lhs: this.equationLhs,
|
|
1152
|
-
rhs: equationRhs,
|
|
1153
|
-
// TODO: For now, fill in an empty string for these two; this is mainly
|
|
1154
|
-
// for compatibility with unit tests that expect empty string instead of
|
|
1155
|
-
// undefined, but this should be revisited
|
|
1156
|
-
units: "",
|
|
1157
|
-
comment: ""
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
return this.equation;
|
|
1161
|
-
}
|
|
1162
|
-
visitSubscriptList(ctx) {
|
|
1163
|
-
if (this.subscripts === void 0) {
|
|
1164
|
-
this.subscripts = ctx.Id().map((id) => id.getText());
|
|
1165
|
-
} else {
|
|
1166
|
-
if (this.exceptSubscriptSets === void 0) {
|
|
1167
|
-
this.exceptSubscriptSets = [];
|
|
1168
|
-
}
|
|
1169
|
-
this.exceptSubscriptSets.push(ctx.Id().map((id) => id.getText()));
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
visitLhs(ctx) {
|
|
1173
|
-
const lhsVarName = ctx.Id().getText();
|
|
1174
|
-
const lhsVarId = canonicalId(lhsVarName);
|
|
1175
|
-
super.visitLhs(ctx);
|
|
1176
|
-
const subscriptNames = this.subscripts;
|
|
1177
|
-
const subscriptRefs = subscriptNames?.map((name) => {
|
|
1178
|
-
return {
|
|
1179
|
-
subName: name,
|
|
1180
|
-
subId: canonicalId(name)
|
|
1181
|
-
};
|
|
1182
|
-
});
|
|
1183
|
-
const exceptSubscriptSets = this.exceptSubscriptSets;
|
|
1184
|
-
const exceptSubscriptRefSets = exceptSubscriptSets?.map((subscriptSet) => {
|
|
1185
|
-
return subscriptSet.map((name) => {
|
|
1186
|
-
return {
|
|
1187
|
-
subName: name,
|
|
1188
|
-
subId: canonicalId(name)
|
|
1189
|
-
};
|
|
1190
|
-
});
|
|
1191
|
-
});
|
|
1192
|
-
this.subscripts = void 0;
|
|
1193
|
-
this.exceptSubscripts = void 0;
|
|
1194
|
-
this.equationLhs = {
|
|
1195
|
-
varDef: {
|
|
1196
|
-
kind: "variable-def",
|
|
1197
|
-
varName: lhsVarName,
|
|
1198
|
-
varId: lhsVarId,
|
|
1199
|
-
subscriptRefs,
|
|
1200
|
-
exceptSubscriptRefSets
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
//
|
|
1205
|
-
// CONST LISTS
|
|
1206
|
-
//
|
|
1207
|
-
visitConstList(ctx) {
|
|
1208
|
-
this.constants = ctx.expr().map((expr) => {
|
|
1209
|
-
const text = expr.getText();
|
|
1210
|
-
const value = parseFloat(text);
|
|
1211
|
-
return {
|
|
1212
|
-
kind: "number",
|
|
1213
|
-
value,
|
|
1214
|
-
text
|
|
1215
|
-
};
|
|
1216
|
-
});
|
|
1217
|
-
this.constListText = ctx.getText();
|
|
1218
|
-
}
|
|
1219
|
-
//
|
|
1220
|
-
// LOOKUPS
|
|
1221
|
-
//
|
|
1222
|
-
getPoint(lookupPoint) {
|
|
1223
|
-
const exprs = lookupPoint.expr();
|
|
1224
|
-
if (exprs.length >= 2) {
|
|
1225
|
-
return [parseFloat(exprs[0].getText()), parseFloat(exprs[1].getText())];
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
visitLookup(ctx) {
|
|
1229
|
-
this.lookupRange = void 0;
|
|
1230
|
-
this.lookupPoints = void 0;
|
|
1231
|
-
if (ctx.lookupRange()) {
|
|
1232
|
-
ctx.lookupRange().accept(this);
|
|
1233
|
-
}
|
|
1234
|
-
if (ctx.lookupPointList()) {
|
|
1235
|
-
ctx.lookupPointList().accept(this);
|
|
1236
|
-
}
|
|
1237
|
-
let range;
|
|
1238
|
-
if (this.lookupRange && this.lookupRange.length === 2) {
|
|
1239
|
-
range = {
|
|
1240
|
-
min: this.lookupRange[0],
|
|
1241
|
-
max: this.lookupRange[1]
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
this.lookupDef = {
|
|
1245
|
-
kind: "lookup-def",
|
|
1246
|
-
range,
|
|
1247
|
-
points: this.lookupPoints
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
visitLookupRange(ctx) {
|
|
1251
|
-
this.lookupRange = ctx.lookupPoint().map((p) => this.getPoint(p));
|
|
1252
|
-
super.visitLookupRange(ctx);
|
|
1253
|
-
}
|
|
1254
|
-
visitLookupPointList(ctx) {
|
|
1255
|
-
this.lookupPoints = ctx.lookupPoint().map((p) => this.getPoint(p));
|
|
1256
|
-
super.visitLookupPointList(ctx);
|
|
1257
|
-
}
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
// src/vensim/parse-vensim-equation.ts
|
|
1261
|
-
function parseVensimEquation(input) {
|
|
1262
|
-
const equationReader = new EquationReader();
|
|
1263
|
-
return equationReader.parse(input);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
// src/vensim/preprocess-vensim.ts
|
|
1267
|
-
var import_split_string = __toESM(require("split-string"), 1);
|
|
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;
|
|
1285
|
-
const rawDefs = splitDefs(input);
|
|
1286
|
-
const vensimDefs = [];
|
|
1287
|
-
const removedBlocks = [];
|
|
1288
|
-
for (const rawDef of rawDefs) {
|
|
1289
|
-
if (shouldRemove(rawDef.text)) {
|
|
1290
|
-
removedBlocks.push(rawDef.text.trim() + "|");
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
const vensimDef = processDef(rawDef);
|
|
1294
|
-
if (vensimDef) {
|
|
1295
|
-
vensimDefs.push(vensimDef);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
return {
|
|
1299
|
-
defs: vensimDefs,
|
|
1300
|
-
removedMacros: macrosResult.removed,
|
|
1301
|
-
removedBlocks
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
function splitDefs(input) {
|
|
1305
|
-
const defTexts = (0, import_split_string.default)(input, { separator: "|", quotes: ['"'], keep: () => true });
|
|
1306
|
-
const rawDefs = [];
|
|
1307
|
-
let lineNum = 1;
|
|
1308
|
-
let currentGroup;
|
|
1309
|
-
for (let defText of defTexts) {
|
|
1310
|
-
if (lineNum === 1) {
|
|
1311
|
-
defText = defText.replace("{UTF-8}", "");
|
|
1312
|
-
}
|
|
1313
|
-
if (defText.includes("\\---/// Sketch")) {
|
|
1314
|
-
break;
|
|
1315
|
-
}
|
|
1316
|
-
const parts = defText.match(/(\s*)(.*)/ms);
|
|
1317
|
-
const leadingLineBreaks = parts[1]?.match(/\r\n|\n|\r/gm);
|
|
1318
|
-
lineNum += leadingLineBreaks?.length || 0;
|
|
1319
|
-
if (defText.includes("********************************************************")) {
|
|
1320
|
-
const groupLines = splitLines(defText).filter((s) => s.trim().length > 0);
|
|
1321
|
-
currentGroup = void 0;
|
|
1322
|
-
if (groupLines.length > 1) {
|
|
1323
|
-
const groupNameLine = groupLines[1];
|
|
1324
|
-
const groupNameParts = groupNameLine.match(/^\s*\.(.*)$/);
|
|
1325
|
-
if (groupNameParts) {
|
|
1326
|
-
currentGroup = groupNameParts[1];
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
} else {
|
|
1330
|
-
rawDefs.push({
|
|
1331
|
-
text: defText,
|
|
1332
|
-
line: lineNum,
|
|
1333
|
-
group: currentGroup
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
const contentLineBreaks = parts[2]?.match(/\r\n|\n|\r/gm);
|
|
1337
|
-
lineNum += contentLineBreaks?.length || 0;
|
|
1338
|
-
}
|
|
1339
|
-
return rawDefs;
|
|
1340
|
-
}
|
|
1341
|
-
function splitLines(input) {
|
|
1342
|
-
return input.split(/\r\n|\n|\r/);
|
|
1343
|
-
}
|
|
1344
|
-
function splitExceptInQuoted(input, sep) {
|
|
1345
|
-
return (0, import_split_string.default)(input, { separator: sep, quotes: ['"'] });
|
|
1346
|
-
}
|
|
1347
|
-
function processBackslashes(input) {
|
|
1348
|
-
const inputLines = splitLines(input);
|
|
1349
|
-
let output = "";
|
|
1350
|
-
let prevLine = "";
|
|
1351
|
-
for (let line of inputLines) {
|
|
1352
|
-
if (prevLine !== "") {
|
|
1353
|
-
line = prevLine + line.trim();
|
|
1354
|
-
prevLine = "";
|
|
1355
|
-
}
|
|
1356
|
-
const continuation = line.match(/\\\s*$/);
|
|
1357
|
-
if (continuation) {
|
|
1358
|
-
prevLine = line.substr(0, continuation.index).replace(/\s+$/, " ");
|
|
1359
|
-
} else {
|
|
1360
|
-
output += line + "\n";
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
return output;
|
|
1364
|
-
}
|
|
1365
|
-
function replaceDelimitedStrings(str, open, close, newStr) {
|
|
1366
|
-
let result = "";
|
|
1367
|
-
let start = 0;
|
|
1368
|
-
let depth = 0;
|
|
1369
|
-
const n = str.length;
|
|
1370
|
-
for (let i = 0; i < n; i++) {
|
|
1371
|
-
if (str.charAt(i) === open) {
|
|
1372
|
-
if (depth === 0) {
|
|
1373
|
-
result += str.substring(start, i);
|
|
1374
|
-
}
|
|
1375
|
-
depth++;
|
|
1376
|
-
} else if (str.charAt(i) === close && depth > 0) {
|
|
1377
|
-
depth--;
|
|
1378
|
-
if (depth === 0) {
|
|
1379
|
-
result += newStr;
|
|
1380
|
-
start = i + 1;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
if (start < n) {
|
|
1385
|
-
result += str.substring(start);
|
|
1386
|
-
}
|
|
1387
|
-
return result;
|
|
1388
|
-
}
|
|
1389
|
-
function reduceWhitespace(input) {
|
|
1390
|
-
return input.replace(/\s\s+/g, " ").trim();
|
|
1391
|
-
}
|
|
1392
|
-
var reWhitespace2 = new RegExp("(\\s|_)+", "g");
|
|
1393
|
-
function keyForDef(def) {
|
|
1394
|
-
let key = def;
|
|
1395
|
-
key = key.replace(/:INTERPOLATE:/g, "");
|
|
1396
|
-
let kind;
|
|
1397
|
-
if (key.includes("=")) {
|
|
1398
|
-
kind = "eqn";
|
|
1399
|
-
key = key.split("=")[0].trim();
|
|
1400
|
-
} else if (key.includes(":")) {
|
|
1401
|
-
kind = "dim";
|
|
1402
|
-
key = key.split(":")[0].trim();
|
|
1403
|
-
} else {
|
|
1404
|
-
kind = "decl";
|
|
1405
|
-
}
|
|
1406
|
-
key = splitExceptInQuoted(key, "(")[0];
|
|
1407
|
-
key = key.replace(/"/g, "");
|
|
1408
|
-
key = key.trim();
|
|
1409
|
-
key = key.replace(/(?<=\[).*?(?=\])/g, (match) => match.replace(/\s/g, ""));
|
|
1410
|
-
key = key.replace(reWhitespace2, "_");
|
|
1411
|
-
key = key.toLowerCase();
|
|
1412
|
-
return { key, kind };
|
|
1413
|
-
}
|
|
1414
|
-
function processDef(rawDef) {
|
|
1415
|
-
let input = rawDef.text;
|
|
1416
|
-
input = input.replace(/:RAW:/g, "");
|
|
1417
|
-
input = replaceDelimitedStrings(input, "{", "}", "");
|
|
1418
|
-
input = input.trim();
|
|
1419
|
-
if (input.length === 0) {
|
|
1420
|
-
return void 0;
|
|
1421
|
-
}
|
|
1422
|
-
input = processBackslashes(input);
|
|
1423
|
-
const parts = input.split("~");
|
|
1424
|
-
if (parts.length < 3) {
|
|
1425
|
-
throw new Error(`Found invalid model definition during preprocessing (missing comment delimiters?):
|
|
1426
|
-
|
|
1427
|
-
${input}`);
|
|
1428
|
-
}
|
|
1429
|
-
const rawDefText = reduceWhitespace(parts[0]);
|
|
1430
|
-
const { key, kind } = keyForDef(rawDefText);
|
|
1431
|
-
const def = `${rawDefText} ~~|`;
|
|
1432
|
-
const units = reduceWhitespace(parts[1]);
|
|
1433
|
-
const comment = reduceWhitespace(parts[2]);
|
|
1434
|
-
const group = rawDef.group;
|
|
1435
|
-
return {
|
|
1436
|
-
key,
|
|
1437
|
-
def,
|
|
1438
|
-
kind,
|
|
1439
|
-
line: rawDef.line,
|
|
1440
|
-
units,
|
|
1441
|
-
comment,
|
|
1442
|
-
...group ? { group } : {}
|
|
1443
|
-
};
|
|
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
|
-
}
|
|
1457
|
-
|
|
1458
|
-
// src/vensim/impl/model-reader.js
|
|
1459
|
-
var import_antlr4_vensim5 = require("antlr4-vensim");
|
|
1460
|
-
var ModelReader = class extends import_antlr4_vensim5.ModelVisitor {
|
|
1461
|
-
/**
|
|
1462
|
-
* @public
|
|
1463
|
-
* @param {import('../vensim-parse-context').VensimParseContext} parseContext An object
|
|
1464
|
-
* that provides access to file system resources (such as external data files) that are
|
|
1465
|
-
* referenced during the parse phase.
|
|
1466
|
-
*/
|
|
1467
|
-
constructor(parseContext) {
|
|
1468
|
-
super();
|
|
1469
|
-
this.parseContext = parseContext;
|
|
1470
|
-
this.dimensions = [];
|
|
1471
|
-
this.equations = [];
|
|
1472
|
-
}
|
|
1473
|
-
/**
|
|
1474
|
-
* Parse the given Vensim model definition and return a `Model` AST node.
|
|
1475
|
-
*
|
|
1476
|
-
* @public
|
|
1477
|
-
* @param {string} modelText A string containing the Vensim model.
|
|
1478
|
-
* @returns {import('../../ast/ast-types').Model} A `Model` AST node.
|
|
1479
|
-
*/
|
|
1480
|
-
/*public*/
|
|
1481
|
-
parse(modelText) {
|
|
1482
|
-
const parser = createAntlrParser(modelText);
|
|
1483
|
-
const modelCtx = parser.model();
|
|
1484
|
-
modelCtx.accept(this);
|
|
1485
|
-
return this.model;
|
|
1486
|
-
}
|
|
1487
|
-
visitModel(ctx) {
|
|
1488
|
-
const subscriptRangesCtx = ctx.subscriptRange();
|
|
1489
|
-
if (subscriptRangesCtx) {
|
|
1490
|
-
const subscriptReader = new SubscriptRangeReader(this.parseContext);
|
|
1491
|
-
for (const subscriptRangeCtx of subscriptRangesCtx) {
|
|
1492
|
-
const dimensionDef = subscriptReader.visitSubscriptRange(subscriptRangeCtx);
|
|
1493
|
-
this.dimensions.push(dimensionDef);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
const equationsCtx = ctx.equation();
|
|
1497
|
-
if (equationsCtx) {
|
|
1498
|
-
const equationReader = new EquationReader();
|
|
1499
|
-
for (const equationCtx of equationsCtx) {
|
|
1500
|
-
const equation = equationReader.visitEquation(equationCtx);
|
|
1501
|
-
this.equations.push(equation);
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
this.model = {
|
|
1505
|
-
dimensions: this.dimensions,
|
|
1506
|
-
equations: this.equations
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
|
|
1511
|
-
// src/vensim/parse-vensim-model.ts
|
|
1512
|
-
function parseVensimModel(input, context, sort = false) {
|
|
1513
|
-
const dimensions = [];
|
|
1514
|
-
const equations = [];
|
|
1515
|
-
const { defs } = preprocessVensimModel(input);
|
|
1516
|
-
if (sort) {
|
|
1517
|
-
defs.sort((a, b) => {
|
|
1518
|
-
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
for (const def of defs) {
|
|
1522
|
-
let parsedModel;
|
|
1523
|
-
try {
|
|
1524
|
-
const modelReader = new ModelReader(context);
|
|
1525
|
-
parsedModel = modelReader.parse(def.def);
|
|
1526
|
-
} catch (e) {
|
|
1527
|
-
let linePart = "";
|
|
1528
|
-
if (e.cause?.code === "VensimParseError") {
|
|
1529
|
-
if (e.cause.line) {
|
|
1530
|
-
linePart += ` at line ${e.cause.line - 1 + def.line}`;
|
|
1531
|
-
if (e.cause.column) {
|
|
1532
|
-
linePart += `, col ${e.cause.column}`;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
const msg = `Failed to parse Vensim model definition${linePart}:
|
|
1537
|
-
${def.def}
|
|
1538
|
-
|
|
1539
|
-
Detail:
|
|
1540
|
-
${e.message}`;
|
|
1541
|
-
throw new Error(msg);
|
|
1542
|
-
}
|
|
1543
|
-
for (const dimensionDef of parsedModel.dimensions) {
|
|
1544
|
-
const group = def.group;
|
|
1545
|
-
dimensions.push({
|
|
1546
|
-
...dimensionDef,
|
|
1547
|
-
comment: def.comment,
|
|
1548
|
-
...group ? { group } : {}
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
for (const equation of parsedModel.equations) {
|
|
1552
|
-
const group = def.group;
|
|
1553
|
-
equations.push({
|
|
1554
|
-
...equation,
|
|
1555
|
-
units: def.units,
|
|
1556
|
-
comment: def.comment,
|
|
1557
|
-
...group ? { group } : {}
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
return {
|
|
1562
|
-
dimensions,
|
|
1563
|
-
equations
|
|
1564
|
-
};
|
|
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
|
-
}
|
|
2150
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2151
|
-
0 && (module.exports = {
|
|
2152
|
-
canonicalFunctionId,
|
|
2153
|
-
canonicalId,
|
|
2154
|
-
canonicalVarId,
|
|
2155
|
-
debugPrintExpr,
|
|
2156
|
-
parseVensimEquation,
|
|
2157
|
-
parseVensimExpr,
|
|
2158
|
-
parseVensimModel,
|
|
2159
|
-
parseVensimSubscriptRange,
|
|
2160
|
-
parseXmileDimensionDef,
|
|
2161
|
-
parseXmileModel,
|
|
2162
|
-
parseXmileVariableDef,
|
|
2163
|
-
preprocessVensimModel,
|
|
2164
|
-
prettyPrintExpr,
|
|
2165
|
-
printExprStats,
|
|
2166
|
-
reduceConditionals,
|
|
2167
|
-
reduceExpr,
|
|
2168
|
-
toPrettyString
|
|
2169
|
-
});
|
|
2170
|
-
//# sourceMappingURL=index.cjs.map
|