@sdeverywhere/parse 0.1.0

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