@sdeverywhere/compile 0.7.10 → 0.7.11

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.
@@ -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
+ }