@sdeverywhere/compile 0.7.10 → 0.7.12
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/package.json +2 -1
- package/src/_shared/helpers.js +0 -4
- package/src/_shared/read-dat.js +11 -2
- package/src/generate/code-gen.js +51 -59
- package/src/generate/direct-data-helpers.js +86 -0
- package/src/generate/equation-gen.js +1 -1
- package/src/generate/expand-var-names.js +115 -0
- package/src/generate/gen-const-list.js +89 -0
- package/src/generate/gen-direct-const.js +91 -0
- package/src/generate/gen-equation.js +273 -0
- package/src/generate/gen-expr.js +851 -0
- package/src/generate/gen-lookup-from-direct.js +105 -0
- package/src/generate/gen-lookup-from-external.js +115 -0
- package/src/generate/gen-lookup-from-points.js +85 -0
- package/src/model/equation-reader.js +27 -22
- package/src/model/model.js +87 -36
- package/src/model/read-equation-fn-delay.js +312 -0
- package/src/model/read-equation-fn-npv.js +67 -0
- package/src/model/read-equation-fn-smooth.js +133 -0
- package/src/model/read-equation-fn-trend.js +44 -0
- package/src/model/read-equation-fn-with-lookup.js +37 -0
- package/src/model/read-equations.js +795 -0
- package/src/model/read-subscripts.js +77 -0
- package/src/model/read-variables.js +264 -0
- package/src/model/reduce-variables.js +166 -0
- package/src/model/toposort.js +1 -1
- package/src/model/variable.js +7 -2
- package/src/parse-and-generate.js +65 -12
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { toPrettyString } from '@sdeverywhere/parse'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
canonicalName,
|
|
5
|
+
canonicalVensimName,
|
|
6
|
+
decanonicalize,
|
|
7
|
+
isSeparatedVar,
|
|
8
|
+
newAuxVarName,
|
|
9
|
+
newLevelVarName
|
|
10
|
+
} from '../_shared/helpers.js'
|
|
11
|
+
|
|
12
|
+
import { separatedVariableIndex } from '../_shared/subscript.js'
|
|
13
|
+
|
|
14
|
+
import Model from './model.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate level and aux variables that implement one of the following `DELAY` function
|
|
18
|
+
* call variants:
|
|
19
|
+
* - DELAY1
|
|
20
|
+
* - DELAY1I
|
|
21
|
+
* - DELAY3
|
|
22
|
+
* - DELAY3I
|
|
23
|
+
*
|
|
24
|
+
* TODO: Docs
|
|
25
|
+
*
|
|
26
|
+
* @param {*} v
|
|
27
|
+
* @param {*} callExpr
|
|
28
|
+
* @param {*} context
|
|
29
|
+
*/
|
|
30
|
+
export function generateDelayVariables(v, callExpr, context) {
|
|
31
|
+
// Get the text representation of each argument expression
|
|
32
|
+
// TODO: Maybe we could skip this step if we built trees for each generated equation instead
|
|
33
|
+
// of reparsing
|
|
34
|
+
const args = callExpr.args.map(toPrettyString)
|
|
35
|
+
let argInput = args[0]
|
|
36
|
+
let argDelay = args[1]
|
|
37
|
+
|
|
38
|
+
let argInit
|
|
39
|
+
if (args.length === 3) {
|
|
40
|
+
// Use the explicit initial value argument
|
|
41
|
+
argInit = `${args[2]}`
|
|
42
|
+
} else {
|
|
43
|
+
// Use the input argument as the initial value
|
|
44
|
+
argInit = `${argInput}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If the LHS includes subscripts, use those same subscripts when generating
|
|
48
|
+
// the new level and aux variables
|
|
49
|
+
let subs
|
|
50
|
+
if (context.eqnLhs.varDef.subscriptRefs) {
|
|
51
|
+
const subNames = context.eqnLhs.varDef.subscriptRefs.map(subRef => subRef.subName)
|
|
52
|
+
subs = `[${subNames.join(',')}]`
|
|
53
|
+
} else {
|
|
54
|
+
subs = ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fnId = callExpr.fnId
|
|
58
|
+
if (fnId === '_DELAY1' || fnId === '_DELAY1I') {
|
|
59
|
+
// Generate 1 level and 1 aux variable that will replace the `DELAY1[I]` function call
|
|
60
|
+
if (isSeparatedVar(v)) {
|
|
61
|
+
// The LHS variable is separated, so we need to generate separated level variable instances
|
|
62
|
+
generateDelay1VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit)
|
|
63
|
+
} else {
|
|
64
|
+
// The LHS variable is normal (non-separated or apply-to-all), so we can create a single
|
|
65
|
+
// level variable instance
|
|
66
|
+
generateDelay1VarsForNormalVar(v, context, subs, argInput, argDelay, argInit)
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// Generate 3 level variables and 4 aux variables that will replace the `DELAY3[I]`
|
|
70
|
+
// function call
|
|
71
|
+
if (isSeparatedVar(v)) {
|
|
72
|
+
// The LHS variable is separated, so we need to generate multiple sets of separated level
|
|
73
|
+
// variable instances
|
|
74
|
+
generateDelay3VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit)
|
|
75
|
+
} else {
|
|
76
|
+
// The LHS variable is normal (non-separated or apply-to-all), so we can create a single
|
|
77
|
+
// set of level variable instances
|
|
78
|
+
generateDelay3VarsForNormalVar(v, context, subs, argInput, argDelay, argInit)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate a single level variable that is used to implement a `DELAY` function call.
|
|
85
|
+
*/
|
|
86
|
+
function generateDelayLevel(context, levelLHS, levelRefId, input, aux, init) {
|
|
87
|
+
const levelEqn = `${levelLHS} = INTEG(${input} - ${aux}, ${init}) ~~|`
|
|
88
|
+
context.defineVariable(levelEqn)
|
|
89
|
+
context.addVarReference(levelRefId)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//
|
|
93
|
+
// DELAY1[I]
|
|
94
|
+
//
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* TODO: Docs
|
|
98
|
+
*/
|
|
99
|
+
function generateDelay1VarsForNormalVar(v, context, subs, argInput, argDelay, argInit) {
|
|
100
|
+
// Generate a single level variable instance
|
|
101
|
+
const varLHS = v.modelLHS
|
|
102
|
+
const levelVarBaseName = newLevelVarName()
|
|
103
|
+
const levelVarRefId = canonicalName(levelVarBaseName)
|
|
104
|
+
const levelLHS = `${levelVarBaseName}${subs}`
|
|
105
|
+
|
|
106
|
+
// TODO: There should be parens around the factors in this init expression
|
|
107
|
+
const argInitTimesDelay = `${argInit} * ${argDelay}`
|
|
108
|
+
generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay)
|
|
109
|
+
v.delayVarRefId = levelVarRefId
|
|
110
|
+
|
|
111
|
+
// Generate an aux variable to hold the delay time expression
|
|
112
|
+
generateDelay1Aux(v, context, subs, argDelay)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* TODO: Docs
|
|
117
|
+
*/
|
|
118
|
+
function generateDelay1VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit) {
|
|
119
|
+
// XXX: This code that deals with separated variables is largely copied from the legacy
|
|
120
|
+
// `equation-reader.js` and modified to work with the AST instead of directly depending
|
|
121
|
+
// on antlr4-vensim constructs. This logic is pretty complex so we should try to refactor
|
|
122
|
+
// or at least add some more fine-grained unit tests for it.
|
|
123
|
+
let varLHS = v.modelLHS
|
|
124
|
+
|
|
125
|
+
const levelVarBaseName = newLevelVarName(v.varName, 1)
|
|
126
|
+
let index
|
|
127
|
+
let sepDim
|
|
128
|
+
let r = subs.match(/\[(.*)\]/)
|
|
129
|
+
if (r) {
|
|
130
|
+
let rhsSubs = r[1].split(',').map(canonicalName)
|
|
131
|
+
for (let rhsSub of rhsSubs) {
|
|
132
|
+
let separatedIndexName = separatedVariableIndex(rhsSub, v, rhsSubs)
|
|
133
|
+
if (separatedIndexName) {
|
|
134
|
+
index = decanonicalize(separatedIndexName)
|
|
135
|
+
sepDim = decanonicalize(rhsSub)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (index) {
|
|
142
|
+
let re = new RegExp(sepDim, 'gi')
|
|
143
|
+
subs = subs.replace(re, index)
|
|
144
|
+
varLHS = varLHS.replace(re, index)
|
|
145
|
+
argInput = argInput.replace(re, index)
|
|
146
|
+
argDelay = argDelay.replace(re, index)
|
|
147
|
+
argInit = argInit.replace(re, index)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const levelLHS = `${levelVarBaseName}${subs}`
|
|
151
|
+
const levelVarRefId = canonicalVensimName(levelLHS)
|
|
152
|
+
Model.addNonAtoAVar(canonicalName(levelVarBaseName), [true])
|
|
153
|
+
|
|
154
|
+
// TODO: There should be parens around the factors in this init expression
|
|
155
|
+
const argInitTimesDelay = `${argInit} * ${argDelay}`
|
|
156
|
+
generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay)
|
|
157
|
+
v.delayVarRefId = levelVarRefId
|
|
158
|
+
|
|
159
|
+
// Generate an aux variable to hold the delay time expression
|
|
160
|
+
generateDelay1Aux(v, context, subs, argDelay)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* TODO: Docs
|
|
165
|
+
*/
|
|
166
|
+
function generateDelay1Aux(v, context, subs, argDelay) {
|
|
167
|
+
const delayTimeVarName = newAuxVarName()
|
|
168
|
+
const delayTimeLHS = `${delayTimeVarName}${subs}`
|
|
169
|
+
const delayTimeVarRefId = canonicalVensimName(delayTimeLHS)
|
|
170
|
+
|
|
171
|
+
v.delayTimeVarName = canonicalName(delayTimeVarName)
|
|
172
|
+
if (isSeparatedVar(v)) {
|
|
173
|
+
// Note: We need to mark this generated aux variable as non-apply-to-all before
|
|
174
|
+
// we define it with `defineVariable` so that the refId is computed correctly
|
|
175
|
+
Model.addNonAtoAVar(v.delayTimeVarName, [true])
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const delayTimeEqn = `${delayTimeLHS} = ${argDelay} ~~|`
|
|
179
|
+
context.defineVariable(delayTimeEqn)
|
|
180
|
+
context.addVarReference(delayTimeVarRefId)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
//
|
|
184
|
+
// DELAY3[I]
|
|
185
|
+
//
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* TODO: Docs
|
|
189
|
+
*/
|
|
190
|
+
function generateDelay3VarsForNormalVar(v, context, subs, argInput, argDelay, argInit) {
|
|
191
|
+
// Generate names for the 3 level variables and 4 aux variables
|
|
192
|
+
const level1 = newLevelVarName()
|
|
193
|
+
const level2 = newLevelVarName()
|
|
194
|
+
const level3 = newLevelVarName()
|
|
195
|
+
const aux1 = newAuxVarName()
|
|
196
|
+
const aux2 = newAuxVarName()
|
|
197
|
+
const aux3 = newAuxVarName()
|
|
198
|
+
const aux4 = newAuxVarName()
|
|
199
|
+
const level1LHS = `${level1}${subs}`
|
|
200
|
+
const level2LHS = `${level2}${subs}`
|
|
201
|
+
const level3LHS = `${level3}${subs}`
|
|
202
|
+
const aux1LHS = `${aux1}${subs}`
|
|
203
|
+
const aux2LHS = `${aux2}${subs}`
|
|
204
|
+
const aux3LHS = `${aux3}${subs}`
|
|
205
|
+
const aux4LHS = `${aux4}${subs}`
|
|
206
|
+
const level1RefId = canonicalName(level1)
|
|
207
|
+
const level2RefId = canonicalName(level2)
|
|
208
|
+
const level3RefId = canonicalName(level3)
|
|
209
|
+
|
|
210
|
+
// Generate level variables
|
|
211
|
+
const delay3Val = `((${argDelay}) / 3)`
|
|
212
|
+
// TODO: There should be parens around these factors
|
|
213
|
+
const initArg = `${argInit} * ${delay3Val}`
|
|
214
|
+
generateDelayLevel(context, level3LHS, level3RefId, aux2LHS, aux3LHS, initArg)
|
|
215
|
+
generateDelayLevel(context, level2LHS, level2RefId, aux1LHS, aux2LHS, initArg)
|
|
216
|
+
generateDelayLevel(context, level1LHS, level1RefId, argInput, aux1LHS, initArg)
|
|
217
|
+
v.delayVarRefId = canonicalName(level3)
|
|
218
|
+
|
|
219
|
+
// Generate aux variable equations using the subs in the generated level vars
|
|
220
|
+
context.defineVariable(`${aux1LHS} = ${level1LHS} / ${delay3Val} ~~|`)
|
|
221
|
+
context.defineVariable(`${aux2LHS} = ${level2LHS} / ${delay3Val} ~~|`)
|
|
222
|
+
context.defineVariable(`${aux3LHS} = ${level3LHS} / ${delay3Val} ~~|`)
|
|
223
|
+
|
|
224
|
+
// Generate an aux variable to hold the delay time expression
|
|
225
|
+
v.delayTimeVarName = canonicalName(aux4)
|
|
226
|
+
const delayTimeVarRefId = canonicalVensimName(aux4LHS)
|
|
227
|
+
context.defineVariable(`${aux4LHS} = ${delay3Val} ~~|`)
|
|
228
|
+
context.addVarReference(delayTimeVarRefId)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* TODO: Docs
|
|
233
|
+
*/
|
|
234
|
+
function generateDelay3VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit) {
|
|
235
|
+
// XXX: This code that deals with separated variables is largely copied from the legacy
|
|
236
|
+
// `equation-reader.js` and modified to work with the AST instead of directly depending
|
|
237
|
+
// on antlr4-vensim constructs. This logic is pretty complex so we should try to refactor
|
|
238
|
+
// or at least add some more fine-grained unit tests for it.
|
|
239
|
+
|
|
240
|
+
// Generate names for the 3 level variables and 4 aux variables
|
|
241
|
+
const level1 = newLevelVarName(v.varName, 1)
|
|
242
|
+
const level2 = newLevelVarName(v.varName, 2)
|
|
243
|
+
const level3 = newLevelVarName(v.varName, 3)
|
|
244
|
+
const aux1 = newAuxVarName(v.varName, 1)
|
|
245
|
+
const aux2 = newAuxVarName(v.varName, 2)
|
|
246
|
+
const aux3 = newAuxVarName(v.varName, 3)
|
|
247
|
+
const aux4 = newAuxVarName(v.varName, 4)
|
|
248
|
+
|
|
249
|
+
let index
|
|
250
|
+
let sepDim
|
|
251
|
+
let r = subs.match(/\[(.*)\]/)
|
|
252
|
+
if (r) {
|
|
253
|
+
let rhsSubs = r[1].split(',').map(x => canonicalName(x))
|
|
254
|
+
for (let rhsSub of rhsSubs) {
|
|
255
|
+
let separatedIndexName = separatedVariableIndex(rhsSub, v, rhsSubs)
|
|
256
|
+
if (separatedIndexName) {
|
|
257
|
+
index = decanonicalize(separatedIndexName)
|
|
258
|
+
sepDim = decanonicalize(rhsSub)
|
|
259
|
+
break
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let delay3Val = `((${argDelay}) / 3)`
|
|
265
|
+
if (index) {
|
|
266
|
+
let re = new RegExp(sepDim, 'gi')
|
|
267
|
+
subs = subs.replace(re, index)
|
|
268
|
+
argInput = argInput.replace(re, index)
|
|
269
|
+
argInit = argInit.replace(re, index)
|
|
270
|
+
delay3Val = delay3Val.replace(re, index)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// TODO: There should be parens around these factors
|
|
274
|
+
const initArg = `${argInit} * ${delay3Val}`
|
|
275
|
+
|
|
276
|
+
const level1LHS = `${level1}${subs}`
|
|
277
|
+
const level2LHS = `${level2}${subs}`
|
|
278
|
+
const level3LHS = `${level3}${subs}`
|
|
279
|
+
|
|
280
|
+
const aux1LHS = `${aux1}${subs}`
|
|
281
|
+
const aux2LHS = `${aux2}${subs}`
|
|
282
|
+
const aux3LHS = `${aux3}${subs}`
|
|
283
|
+
const aux4LHS = `${aux4}${subs}`
|
|
284
|
+
|
|
285
|
+
const level1RefId = canonicalVensimName(level1LHS)
|
|
286
|
+
const level2RefId = canonicalVensimName(level2LHS)
|
|
287
|
+
const level3RefId = canonicalVensimName(level3LHS)
|
|
288
|
+
|
|
289
|
+
Model.addNonAtoAVar(canonicalName(level1), [true])
|
|
290
|
+
Model.addNonAtoAVar(canonicalName(level2), [true])
|
|
291
|
+
Model.addNonAtoAVar(canonicalName(level3), [true])
|
|
292
|
+
|
|
293
|
+
// Generate level variables
|
|
294
|
+
generateDelayLevel(context, level3LHS, level3RefId, aux2LHS, aux3LHS, initArg)
|
|
295
|
+
generateDelayLevel(context, level2LHS, level2RefId, aux1LHS, aux2LHS, initArg)
|
|
296
|
+
generateDelayLevel(context, level1LHS, level1RefId, argInput, aux1LHS, initArg)
|
|
297
|
+
v.delayVarRefId = level3RefId
|
|
298
|
+
|
|
299
|
+
// Generate aux variable equations using the subs in the generated level vars
|
|
300
|
+
context.defineVariable(`${aux1LHS} = ${level1LHS} / ${delay3Val} ~~|`)
|
|
301
|
+
context.defineVariable(`${aux2LHS} = ${level2LHS} / ${delay3Val} ~~|`)
|
|
302
|
+
context.defineVariable(`${aux3LHS} = ${level3LHS} / ${delay3Val} ~~|`)
|
|
303
|
+
|
|
304
|
+
// Generate an aux variable to hold the delay time expression
|
|
305
|
+
v.delayTimeVarName = canonicalName(aux4)
|
|
306
|
+
if (isSeparatedVar(v)) {
|
|
307
|
+
Model.addNonAtoAVar(v.delayTimeVarName, [true])
|
|
308
|
+
}
|
|
309
|
+
const delayTimeVarRefId = canonicalVensimName(aux4LHS)
|
|
310
|
+
context.defineVariable(`${aux4LHS} = ${delay3Val} ~~|`)
|
|
311
|
+
context.addVarReference(delayTimeVarRefId)
|
|
312
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { toPrettyString } from '@sdeverywhere/parse'
|
|
2
|
+
|
|
3
|
+
import { canonicalName, newAuxVarName, newLevelVarName } from '../_shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate two level variables and one aux that implement an `NPV` function call.
|
|
7
|
+
*
|
|
8
|
+
* TODO: Docs
|
|
9
|
+
*
|
|
10
|
+
* @param {*} v
|
|
11
|
+
* @param {*} callExpr
|
|
12
|
+
* @param {*} context
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export function generateNpvVariables(v, callExpr, context) {
|
|
16
|
+
// Get the text representation of each argument expression
|
|
17
|
+
// TODO: Maybe we could skip this step if we built trees for each generated equation instead
|
|
18
|
+
// of reparsing
|
|
19
|
+
const args = callExpr.args.map(toPrettyString)
|
|
20
|
+
const argStream = args[0]
|
|
21
|
+
const argDiscountRate = args[1]
|
|
22
|
+
const argInitVal = args[2]
|
|
23
|
+
const argFactor = args[3]
|
|
24
|
+
|
|
25
|
+
// TODO: Generate subscripts
|
|
26
|
+
// const subs = this.genSubs(stream, discountRate, initVal, factor)
|
|
27
|
+
const subs = ''
|
|
28
|
+
|
|
29
|
+
// Level 1:
|
|
30
|
+
// df = INTEG((-df * discount rate) / (1 + discount rate * TIME STEP), 1)
|
|
31
|
+
const dfVarName = newLevelVarName()
|
|
32
|
+
const dfVarId = canonicalName(dfVarName)
|
|
33
|
+
const dfLHS = `${dfVarName}${subs}`
|
|
34
|
+
// TODO: There should be parens around the `discount rate` argument in case it is an expression
|
|
35
|
+
// and not a simple constant
|
|
36
|
+
const dfEqn = `${dfLHS} = INTEG((-${dfLHS} * ${argDiscountRate}) / (1 + ${argDiscountRate} * TIME STEP), 1) ~~|`
|
|
37
|
+
context.defineVariable(dfEqn)
|
|
38
|
+
|
|
39
|
+
// Level 2:
|
|
40
|
+
// ncum = INTEG(stream * df, init val)
|
|
41
|
+
const ncumVarName = newLevelVarName()
|
|
42
|
+
const ncumVarId = canonicalName(ncumVarName)
|
|
43
|
+
const ncumLHS = `${ncumVarName}${subs}`
|
|
44
|
+
const ncumEqn = `${ncumLHS} = INTEG(${argStream} * ${dfLHS}, ${argInitVal}) ~~|`
|
|
45
|
+
context.defineVariable(ncumEqn)
|
|
46
|
+
|
|
47
|
+
// Aux:
|
|
48
|
+
// npv = (ncum + stream * TIME STEP * df) * factor
|
|
49
|
+
const auxVarName = newAuxVarName()
|
|
50
|
+
const auxVarId = canonicalName(auxVarName)
|
|
51
|
+
const auxLHS = `${auxVarName}${subs}`
|
|
52
|
+
// TODO: There should be parens around the `stream` argument in case it is an expression
|
|
53
|
+
// and not a simple constant
|
|
54
|
+
const auxEqn = `${auxLHS} = (${ncumVarName} + ${argStream} * TIME STEP * ${dfVarName}) * ${argFactor} ~~|`
|
|
55
|
+
context.defineVariable(auxEqn)
|
|
56
|
+
v.npvVarName = auxVarId
|
|
57
|
+
|
|
58
|
+
// Add references to the generated variables
|
|
59
|
+
// TODO: These are added in a different order than how the variables were defined because
|
|
60
|
+
// this is the order that the legacy reader used, and we're trying to be compatible
|
|
61
|
+
context.addVarReference(ncumVarId)
|
|
62
|
+
context.addVarReference(dfVarId)
|
|
63
|
+
context.addVarReference(auxVarId)
|
|
64
|
+
|
|
65
|
+
// TODO: Check on this comment from the legacy reader to see if it's still applicable:
|
|
66
|
+
// If they have subscripts, the refIds are still just the var name, because they are apply-to-all arrays.
|
|
67
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { toPrettyString } from '@sdeverywhere/parse'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
canonicalName,
|
|
5
|
+
canonicalVensimName,
|
|
6
|
+
decanonicalize,
|
|
7
|
+
isSeparatedVar,
|
|
8
|
+
newLevelVarName
|
|
9
|
+
} from '../_shared/helpers.js'
|
|
10
|
+
|
|
11
|
+
import { separatedVariableIndex } from '../_shared/subscript.js'
|
|
12
|
+
|
|
13
|
+
import Model from './model.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate level and aux variables that implement one of the following `SMOOTH` function
|
|
17
|
+
* call variants:
|
|
18
|
+
* - SMOOTH
|
|
19
|
+
* - SMOOTHI
|
|
20
|
+
* - SMOOTH3
|
|
21
|
+
* - SMOOTH3I
|
|
22
|
+
*
|
|
23
|
+
* TODO: Docs
|
|
24
|
+
*
|
|
25
|
+
* @param {*} v
|
|
26
|
+
* @param {*} callExpr
|
|
27
|
+
* @param {*} context
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
export function generateSmoothVariables(v, callExpr, context) {
|
|
31
|
+
// Get the text representation of each argument expression
|
|
32
|
+
const args = callExpr.args.map(toPrettyString)
|
|
33
|
+
const argInput = args[0]
|
|
34
|
+
const argDelay = args[1]
|
|
35
|
+
|
|
36
|
+
let argInit
|
|
37
|
+
if (args.length === 3) {
|
|
38
|
+
// Use the explicit initial value argument
|
|
39
|
+
argInit = args[2]
|
|
40
|
+
} else {
|
|
41
|
+
// Use the input argument as the initial value
|
|
42
|
+
argInit = args[0]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fnId = callExpr.fnId
|
|
46
|
+
if (fnId === '_SMOOTH' || fnId === '_SMOOTHI') {
|
|
47
|
+
// Generate 1 level variable that will replace the `SMOOTH[I]` function call
|
|
48
|
+
const levelVarName = generateSmoothLevel(v, context, argInput, argDelay, argInit, 1)
|
|
49
|
+
// For `SMOOTH[I]`, the smoothVarRefId is the level var's refId
|
|
50
|
+
v.smoothVarRefId = levelVarName[0]
|
|
51
|
+
} else {
|
|
52
|
+
// Generate 3 level variables that will replace the `SMOOTH3[I]` function call
|
|
53
|
+
const delay3Val = `(${argDelay} / 3)`
|
|
54
|
+
const level1VarName = generateSmoothLevel(v, context, argInput, delay3Val, argInit, 1)
|
|
55
|
+
const level2VarName = generateSmoothLevel(v, context, level1VarName[1], delay3Val, argInit, 2)
|
|
56
|
+
const level3VarName = generateSmoothLevel(v, context, level2VarName[1], delay3Val, argInit, 3)
|
|
57
|
+
// For `SMOOTH3[I]`, the smoothVarRefId is the final level var's refId
|
|
58
|
+
v.smoothVarRefId = level3VarName[0]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a single level variable that is used to implement a `SMOOTH` function call.
|
|
64
|
+
*/
|
|
65
|
+
function generateSmoothLevel(v, context, argInput, argDelay, argInit, levelNumber) {
|
|
66
|
+
// XXX: This code that deals with separated variables is largely copied from the legacy
|
|
67
|
+
// `equation-reader.js` and modified to work with the AST instead of directly depending
|
|
68
|
+
// on antlr4-vensim constructs. This logic is pretty complex so we should try to refactor
|
|
69
|
+
// or at least add some more fine-grained unit tests for it.
|
|
70
|
+
let subs = context.extractSubscriptsFromVarNames(argInput, argDelay, argInit)
|
|
71
|
+
|
|
72
|
+
// For SMOOTH3, the previous level is the input for level number 2 and 3. Add RHS subscripts.
|
|
73
|
+
if (levelNumber > 1 && subs.length > 0) {
|
|
74
|
+
argInput = `${argInput}${subs}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let levelVarBaseName
|
|
78
|
+
let levelLHS
|
|
79
|
+
let levelVarRefId
|
|
80
|
+
if (isSeparatedVar(v)) {
|
|
81
|
+
// Levels generated by separated vars are also separated. We have to compute the indices here instead
|
|
82
|
+
// of using the dimension on the LHS and letting addVariable do it, so that the whole array of
|
|
83
|
+
// separated variables are not added for each visit here by an already-separated index.
|
|
84
|
+
// Start by getting a level var based on the var name, so it is the same for all separated levels.
|
|
85
|
+
levelVarBaseName = newLevelVarName(v.varName, levelNumber)
|
|
86
|
+
|
|
87
|
+
// Replace the dimension in the generated variable subscript with the separated index from the LHS.
|
|
88
|
+
// Find the index in the LHS that was expanded from the separation dimension.
|
|
89
|
+
let index
|
|
90
|
+
let sepDim
|
|
91
|
+
let r = subs.match(/\[(.*)\]/)
|
|
92
|
+
if (r) {
|
|
93
|
+
let rhsSubs = r[1].split(',').map(x => canonicalName(x))
|
|
94
|
+
for (let rhsSub of rhsSubs) {
|
|
95
|
+
let separatedIndexName = separatedVariableIndex(rhsSub, v, rhsSubs)
|
|
96
|
+
if (separatedIndexName) {
|
|
97
|
+
index = decanonicalize(separatedIndexName)
|
|
98
|
+
sepDim = decanonicalize(rhsSub)
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Use the Vensim form of the index in the LHS and in all arguments
|
|
105
|
+
if (index) {
|
|
106
|
+
let re = new RegExp(`\\[(.*?)${sepDim}(.*?)\\]`, 'gi')
|
|
107
|
+
let replacement = `[$1${index}$2]`
|
|
108
|
+
subs = subs.replace(re, replacement)
|
|
109
|
+
argInput = argInput.replace(re, replacement)
|
|
110
|
+
argDelay = argDelay.replace(re, replacement)
|
|
111
|
+
argInit = argInit.replace(re, replacement)
|
|
112
|
+
}
|
|
113
|
+
levelLHS = `${levelVarBaseName}${subs}`
|
|
114
|
+
levelVarRefId = canonicalVensimName(levelLHS)
|
|
115
|
+
} else {
|
|
116
|
+
// In the normal case, generate a unique variable name for the level var
|
|
117
|
+
levelVarBaseName = newLevelVarName()
|
|
118
|
+
levelLHS = `${levelVarBaseName}${subs}`
|
|
119
|
+
// If it has subscripts, the refId is still just the var name, because it is an
|
|
120
|
+
// apply-to-all array
|
|
121
|
+
levelVarRefId = canonicalName(levelVarBaseName)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Generate the level variable
|
|
125
|
+
const levelEqn = `${levelLHS} = INTEG((${argInput} - ${levelLHS}) / ${argDelay}, ${argInit}) ~~|`
|
|
126
|
+
if (isSeparatedVar(v)) {
|
|
127
|
+
Model.addNonAtoAVar(canonicalName(levelVarBaseName), [true])
|
|
128
|
+
}
|
|
129
|
+
context.defineVariable(levelEqn)
|
|
130
|
+
context.addVarReference(levelVarRefId)
|
|
131
|
+
|
|
132
|
+
return [levelVarRefId, levelVarBaseName]
|
|
133
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { toPrettyString } from '@sdeverywhere/parse'
|
|
2
|
+
|
|
3
|
+
import { canonicalName, newAuxVarName, newLevelVarName } from '../_shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate two level variables and one aux that implement an `NPV` function call.
|
|
7
|
+
*
|
|
8
|
+
* TODO: Docs
|
|
9
|
+
*
|
|
10
|
+
* @param {*} v
|
|
11
|
+
* @param {*} callExpr
|
|
12
|
+
* @param {*} context
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export function generateTrendVariables(v, callExpr, context) {
|
|
16
|
+
// Get the text representation of each argument expression
|
|
17
|
+
const args = callExpr.args.map(toPrettyString)
|
|
18
|
+
const argInput = args[0]
|
|
19
|
+
const argAvgTime = args[1]
|
|
20
|
+
const argInitVal = args[2]
|
|
21
|
+
|
|
22
|
+
// Generate the subscripts that will be used on the LHS of the generated variables
|
|
23
|
+
// by examining the subscripts of the variables used in arguments to the `TREND` call
|
|
24
|
+
const subs = context.extractSubscriptsFromVarNames(argInput, argAvgTime, argInitVal)
|
|
25
|
+
|
|
26
|
+
// Generate a level variable that will be used in place of the `TREND` call
|
|
27
|
+
const levelVarName = newLevelVarName()
|
|
28
|
+
const levelLHS = `${levelVarName}${subs}`
|
|
29
|
+
// TODO: There should be parens around the arguments in this equation in case any of them
|
|
30
|
+
// is an expression and not a simple constant
|
|
31
|
+
const levelEqn = `${levelLHS} = INTEG((${argInput} - ${levelLHS}) / ${argAvgTime}, ${argInput} / (1 + ${argInitVal} * ${argAvgTime})) ~~|`
|
|
32
|
+
context.defineVariable(levelEqn)
|
|
33
|
+
context.addVarReference(canonicalName(levelVarName))
|
|
34
|
+
|
|
35
|
+
// Generate a aux variable that will be used in place of the `TREND` call
|
|
36
|
+
const auxVarName = newAuxVarName()
|
|
37
|
+
const auxLHS = `${auxVarName}${subs}`
|
|
38
|
+
// TODO: There should be parens around the arguments in this equation in case any of them
|
|
39
|
+
// is an expression and not a simple constant
|
|
40
|
+
const auxEqn = `${auxLHS} = ZIDZ(${argInput} - ${levelLHS}, ${argAvgTime} * ABS(${levelLHS})) ~~|`
|
|
41
|
+
context.defineVariable(auxEqn)
|
|
42
|
+
v.trendVarName = canonicalName(auxVarName)
|
|
43
|
+
context.addVarReference(v.trendVarName)
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toPrettyString } from '@sdeverywhere/parse'
|
|
2
|
+
|
|
3
|
+
import { canonicalName, newLookupVarName } from '../_shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a lookup definition to augment a `WITH LOOKUP` function call.
|
|
7
|
+
*
|
|
8
|
+
* TODO: Docs
|
|
9
|
+
*
|
|
10
|
+
* @param {*} v
|
|
11
|
+
* @param {*} callExpr
|
|
12
|
+
* @param {*} context
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export function generateLookup(v, callExpr, context) {
|
|
16
|
+
// Extract the lookup data from the second argument
|
|
17
|
+
const lookupArg = callExpr.args[1]
|
|
18
|
+
if (lookupArg.kind !== 'lookup-def') {
|
|
19
|
+
throw new Error(`Expected lookup data as second argument in WITH LOOKUP call, but got ${lookupArg.kind}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Generate a new lookup variable using the data points from the lookup argument
|
|
23
|
+
const lookupVarName = newLookupVarName()
|
|
24
|
+
const lookupDef = toPrettyString(lookupArg, { compact: true })
|
|
25
|
+
const lookupEqn = `${lookupVarName}${lookupDef} ~~|`
|
|
26
|
+
context.defineVariable(lookupEqn)
|
|
27
|
+
|
|
28
|
+
// Keep track of all lookup variables that are referenced. This will be used later to decide
|
|
29
|
+
// whether a lookup variable needs to be included in generated code.
|
|
30
|
+
const lookupArgVarName = canonicalName(lookupVarName)
|
|
31
|
+
v.lookupArgVarName = lookupArgVarName
|
|
32
|
+
if (v.referencedLookupVarNames) {
|
|
33
|
+
v.referencedLookupVarNames.push(lookupArgVarName)
|
|
34
|
+
} else {
|
|
35
|
+
v.referencedLookupVarNames = [lookupArgVarName]
|
|
36
|
+
}
|
|
37
|
+
}
|