@sdeverywhere/compile 0.7.20 → 0.7.22

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.
@@ -1,5 +1,5 @@
1
1
  import { cdbl, newTmpVarName } from '../_shared/helpers.js'
2
- import { extractMarkedDims, isDimension, isIndex, normalizeSubscripts, sub } from '../_shared/subscript.js'
2
+ import { extractMarkedDims, isDimension, isIndex, sub } from '../_shared/subscript.js'
3
3
 
4
4
  import Model from '../model/model.js'
5
5
 
@@ -12,8 +12,6 @@ import Model from '../model/model.js'
12
12
  * @param {string} cLhs The C/JS code for the LHS variable reference.
13
13
  * @param {LoopIndexVars} loopIndexVars The loop index state used for LHS dimensions.
14
14
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions (that use marked dimensions).
15
- * @param {() => void} resetMarkedDims Function that resets the marked dimension state.
16
- * @param {(dimId: string) => void} addMarkedDim Function that adds the given dimension to the set of marked dimensions.
17
15
  * @param {(s: string) => void} emitPreInnerLoop Function that will cause the given code to be appended to the chunk that
18
16
  * precedes the generated inner loop for the equation.
19
17
  * @param {(s: string) => void} emitPreFormula Function that will cause the given code to be appended to the chunk that
@@ -69,7 +67,7 @@ export function generateExpr(expr, ctx) {
69
67
  // plus one (since Vensim indices are one-based).
70
68
  const dimId = expr.varId
71
69
  const indexCode = ctx.cVarIndex(dimId)
72
- return `(${indexCode} + 1)`
70
+ return `(${indexExpr(indexCode, ctx)} + 1)`
73
71
  } else if (isIndex(expr.varId)) {
74
72
  // This is a reference to a subscript/index that is being used in expression position.
75
73
  // In place of the subscript, emit the numeric index value of the subscript plus one
@@ -646,7 +644,6 @@ function generateArrayFunctionCall(callExpr, ctx) {
646
644
  // Open the array function loop(s)
647
645
  const indexDecl = ctx.outFormat === 'js' ? 'let' : 'size_t'
648
646
  for (const markedDimId of markedDimIds) {
649
- ctx.addMarkedDim(markedDimId)
650
647
  const n = sub(markedDimId).size
651
648
  const i = ctx.arrayIndexVars.index(markedDimId)
652
649
  ctx.emitPreFormula(` for (${indexDecl} ${i} = 0; ${i} < ${n}; ${i}++) {`)
@@ -691,9 +688,6 @@ function generateArrayFunctionCall(callExpr, ctx) {
691
688
  ctx.emitPreFormula(` }`)
692
689
  }
693
690
 
694
- // Reset marked dim state
695
- ctx.resetMarkedDims()
696
-
697
691
  if (returnCode) {
698
692
  // Emit the expression defined above in place of the array function
699
693
  return returnCode
@@ -747,7 +741,7 @@ function generateVectorElmMapCall(callExpr, ctx) {
747
741
 
748
742
  // The `VECTOR ELM MAP` function replaces one subscript with a calculated offset from
749
743
  // a base index
750
- const rhsSubIds = normalizeSubscripts(vecSubIds)
744
+ const rhsSubIds = vecSubIds
751
745
  const cSubscripts = rhsSubIds.map(rhsSubId => {
752
746
  if (isIndex(rhsSubId)) {
753
747
  let indexDecl
@@ -838,35 +832,55 @@ function generateAllocateAvailableCall(callExpr, ctx) {
838
832
  }
839
833
  }
840
834
 
841
- // Process the request argument
835
+ // Given a C/JS variable reference string (e.g., '_var[i][j]'), return that
836
+ // string without the last N array index parts
837
+ function cVarRefWithoutLastIndices(arg, count) {
838
+ const varRef = ctx.cVarRef(arg)
839
+ const origIndexParts = Model.splitRefId(varRef).subscripts
840
+ if (origIndexParts < count) {
841
+ throw new Error(`ALLOCATE AVAILABLE argument '${arg}' should have at least ${count} subscripts`)
842
+ }
843
+ const newIndexParts = origIndexParts.slice(0, -count)
844
+ if (newIndexParts.length > 0) {
845
+ return `${arg.varId}${newIndexParts.map(x => `[${x}]`).join('')}`
846
+ } else {
847
+ return arg.varId
848
+ }
849
+ }
850
+
851
+ // Process the request argument. Only include subscripts up until the last one;
852
+ // the implementation function will iterate over the requesters array.
842
853
  const reqArg = validateArg(0, 'req')
843
- const reqRefId = reqArg.varId
844
- const reqSubIds = reqArg.subscriptRefs.map(subRef => subRef.subId)
854
+ const reqRef = cVarRefWithoutLastIndices(reqArg, 1)
845
855
 
846
- // Process the priority argument
847
- const priorityArg = validateArg(1, 'priority')
848
- const priorityRefId = priorityArg.varId
856
+ // Process the pp (priority profile) argument. Only include subscripts up until the
857
+ // second to last one; the implementation function will iterate over the priority
858
+ // profile array.
859
+ const ppArg = validateArg(1, 'pp')
860
+ const ppRef = cVarRefWithoutLastIndices(ppArg, 2)
849
861
 
850
- // Process the avail argument
862
+ // Process the avail argument; include any subscripts
851
863
  const availArg = validateArg(2, 'avail')
852
- const availRefId = availArg.varId
864
+ const availRef = ctx.cVarRef(availArg)
853
865
 
854
- // The `ALLOCATE AVAILABLE` function iterates over the subscript in its first arg
855
- const dimId = reqSubIds[0]
856
- const subIndex = ctx.loopIndexVars.index(dimId)
866
+ // The `ALLOCATE AVAILABLE` function iterates over the last subscript in its first arg.
867
+ // The `readEquation` process will have already verified that the last dimension matches
868
+ // the last dimension for the LHS.
869
+ const allocDimId = reqArg.subscriptRefs[reqArg.subscriptRefs.length - 1].subId
870
+ const allocLoopIndexVar = ctx.loopIndexVars.index(allocDimId)
857
871
 
858
872
  // Generate the code that is emitted before the entire block (before any loops are opened)
859
873
  const tmpVarId = newTmpVarName()
860
- const dimSize = sub(dimId).size
874
+ const numRequesters = sub(allocDimId).size
861
875
  switch (ctx.outFormat) {
862
876
  case 'c':
863
877
  ctx.emitPreInnerLoop(
864
- ` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${reqRefId}, (double*)${priorityRefId}, ${availRefId}, ${dimSize});`
878
+ ` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${reqRef}, (double*)${ppRef}, ${availRef}, ${numRequesters});`
865
879
  )
866
880
  break
867
881
  case 'js':
868
882
  ctx.emitPreInnerLoop(
869
- ` let ${tmpVarId} = fns.ALLOCATE_AVAILABLE(${reqRefId}, ${priorityRefId}, ${availRefId}, ${dimSize});`
883
+ ` let ${tmpVarId} = fns.ALLOCATE_AVAILABLE(${reqRef}, ${ppRef}, ${availRef}, ${numRequesters});`
870
884
  )
871
885
  break
872
886
  default:
@@ -874,7 +888,7 @@ function generateAllocateAvailableCall(callExpr, ctx) {
874
888
  }
875
889
 
876
890
  // Generate the RHS expression used in the inner loop
877
- return `${tmpVarId}[${dimId}[${subIndex}]]`
891
+ return `${tmpVarId}[${allocDimId}[${allocLoopIndexVar}]]`
878
892
  }
879
893
 
880
894
  /**
@@ -1006,3 +1020,32 @@ function minFunc(ctx) {
1006
1020
  throw new Error(`Unhandled output format '${ctx.outFormat}'`)
1007
1021
  }
1008
1022
  }
1023
+
1024
+ /**
1025
+ * Return the C or JS code for a subscript or dimension (loop index variable) used in
1026
+ * expression position.
1027
+ *
1028
+ * @param {string} indexValue The index number or code.
1029
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
1030
+ * @return {string} The generated C/JS code.
1031
+ */
1032
+ function indexExpr(indexValue, ctx) {
1033
+ switch (ctx.outFormat) {
1034
+ case 'c':
1035
+ // In the C case, we need to cast to double since the index variable will be
1036
+ // of type `size_t`, which is an unsigned type, but we want a signed type for
1037
+ // the rare cases where math is involved that makes it go negative
1038
+ if (isNaN(indexValue)) {
1039
+ // This is a (non-numeric) loop index variable reference, so cast to double
1040
+ return `((double)${indexValue})`
1041
+ } else {
1042
+ // This is a numeric index, no cast is necessary
1043
+ return indexValue
1044
+ }
1045
+ case 'js':
1046
+ // In the JS case, no cast is necessary
1047
+ return indexValue
1048
+ default:
1049
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
1050
+ }
1051
+ }
@@ -16,10 +16,23 @@ import { isDimension, isTrivialDimension, sub } from '../_shared/subscript.js'
16
16
  */
17
17
  export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndexVars, outFormat) {
18
18
  if (variable.points.length === 0) {
19
- throw new Error(`Empty lookup data array for ${variable.modelLHS}`)
20
- }
21
-
22
- if (copy) {
19
+ // In the case where the lookup data array is empty (typically this only occurs in the
20
+ // case of game inputs), generate a new empty lookup
21
+ if (mode === 'decl') {
22
+ // Nothing to emit in decl mode
23
+ return []
24
+ } else if (mode === 'init-lookups') {
25
+ // In init mode, generate a new empty lookup
26
+ switch (outFormat) {
27
+ case 'c':
28
+ return [` ${varLhs} = __new_lookup(0, /*copy=*/false, NULL);`]
29
+ case 'js':
30
+ return [` ${varLhs} = fns.createLookup(0, undefined);`]
31
+ default:
32
+ throw new Error(`Unhandled output format '${outFormat}'`)
33
+ }
34
+ }
35
+ } else if (copy) {
23
36
  // Inline the data points and copy them when initializing the lookup
24
37
  if (mode === 'decl') {
25
38
  // Nothing to emit in decl mode
@@ -1,5 +1,17 @@
1
1
  import { isDimension } from '../_shared/subscript.js'
2
2
 
3
+ /**
4
+ * Mapping of dimension IDs to loop index variables. The `indexVars` is an array
5
+ * of C/JS loop index variable names that will be generated. For example, suppose
6
+ * `indexVars` is ['i', 'j', 'k'] and we have an equation:
7
+ * x[DimA, DimB, DimC] = y[DimB, DimC, DimA] ~~|
8
+ * This class will map the index variables according to the order in which the
9
+ * `index` method is called:
10
+ * const vars = new LoopIndexVars(['i', 'j', 'k'])
11
+ * vars.index('_dima') // returns 'i'
12
+ * vars.index('_dimb') // returns 'j'
13
+ * vars.index('_dimc') // returns 'k'
14
+ */
3
15
  export default class LoopIndexVars {
4
16
  constructor(indexVars) {
5
17
  this.loopIndexVars = this.loopIndexVarGen(indexVars)
package/src/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  // Copyright (c) 2022 Climate Interactive / New Venture Fund
2
2
 
3
+ // XXX: For now we re-export the preprocess function from the parse package
4
+ // mainly for use by the cli package (so that we don't need to have the cli
5
+ // package directly depend on the parse package)
6
+ export { preprocessVensimModel } from '@sdeverywhere/parse'
7
+
3
8
  export { canonicalName } from './_shared/helpers.js'
4
9
  export { readDat } from './_shared/read-dat.js'
5
- export { preprocessModel } from './preprocess/preprocessor.js'
6
10
  export { generateCode } from './generate/gen-code.js'
7
11
  export { parseAndGenerate, parseModel, printNames } from './parse-and-generate.js'
8
12
 
@@ -10,7 +10,6 @@ import {
10
10
  indexNamesForSubscript,
11
11
  isDimension,
12
12
  isIndex,
13
- normalizeSubscripts,
14
13
  sub,
15
14
  subscriptFamilies
16
15
  } from '../_shared/subscript.js'
@@ -465,7 +464,7 @@ function findNonAtoAVars() {
465
464
  // Find variables with multiple instances with the same var name, which makes them
466
465
  // elements in a non-apply-to-all array. This function constructs the nonAtoANames list.
467
466
  function areSubsEqual(vars, i) {
468
- // Scan the subscripts for each var at position i in normal order.
467
+ // Scan the subscripts for each var at position i in the original order.
469
468
  // Return true if the subscript is the same for all vars with that name.
470
469
  let subscript = vars[0].subscripts[i]
471
470
  for (let v of vars) {
@@ -478,7 +477,7 @@ function findNonAtoAVars() {
478
477
  R.forEach(name => {
479
478
  let vars = varsWithName(name)
480
479
  if (vars.length > 1) {
481
- // This is a non-apply-to-all array. Construct the exansion dims array for it.
480
+ // This is a non-apply-to-all array. Construct the expansion dims array for it.
482
481
  // The expansion dim is true at each dim position where the subscript varies.
483
482
  let numDims = vars[0].subscripts.length
484
483
  let expansionDims = []
@@ -665,8 +664,6 @@ function splitRefId(refId) {
665
664
  varName = m[0]
666
665
  }
667
666
  }
668
- // Put subscripts in normal order.
669
- subscripts = normalizeSubscripts(subscripts)
670
667
  return { varName, subscripts }
671
668
  }
672
669
  function varWithName(varName) {
@@ -744,7 +741,6 @@ function cName(vensimVarName) {
744
741
  if (matches[2]) {
745
742
  // The variable name includes subscripts, so split them into individual IDs
746
743
  let cSubIds = matches[2].split(',').map(x => canonicalName(x))
747
- cSubIds = normalizeSubscripts(cSubIds)
748
744
  // If a subscript is an index, convert it to an index number to match Vensim data exports
749
745
  let cSubIdParts = cSubIds.map(cSubId => {
750
746
  return isIndex(cSubId) ? `[${sub(cSubId).value}]` : `[${cSubId}]`
@@ -81,12 +81,13 @@ export function generateDelayVariables(v, callExpr, context) {
81
81
  }
82
82
 
83
83
  /**
84
- * Generate a single level variable that is used to implement a `DELAY` function call.
84
+ * Generate equation text for a single level variable that is used to implement a `DELAY`
85
+ * function call, and add a reference to `levelRefId`.
85
86
  */
86
87
  function generateDelayLevel(context, levelLHS, levelRefId, input, aux, init) {
87
88
  const levelEqn = `${levelLHS} = INTEG(${input} - ${aux}, ${init}) ~~|`
88
- context.defineVariable(levelEqn)
89
89
  context.addVarReference(levelRefId)
90
+ return levelEqn
90
91
  }
91
92
 
92
93
  //
@@ -94,7 +95,8 @@ function generateDelayLevel(context, levelLHS, levelRefId, input, aux, init) {
94
95
  //
95
96
 
96
97
  /**
97
- * TODO: Docs
98
+ * Generate and define variables that are needed to implement a `DELAY1[I]` function call
99
+ * for a non-subscripted or apply-to-all variable.
98
100
  */
99
101
  function generateDelay1VarsForNormalVar(v, context, subs, argInput, argDelay, argInit) {
100
102
  // Generate a single level variable instance
@@ -103,17 +105,23 @@ function generateDelay1VarsForNormalVar(v, context, subs, argInput, argDelay, ar
103
105
  const levelVarRefId = canonicalName(levelVarBaseName)
104
106
  const levelLHS = `${levelVarBaseName}${subs}`
105
107
 
108
+ // Generate the equation text for the delay level variable
109
+ const eqns = []
106
110
  // TODO: There should be parens around the factors in this init expression
107
111
  const argInitTimesDelay = `${argInit} * ${argDelay}`
108
- generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay)
112
+ eqns.push(generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay))
109
113
  v.delayVarRefId = levelVarRefId
110
114
 
111
115
  // Generate an aux variable to hold the delay time expression
112
- generateDelay1Aux(v, context, subs, argDelay)
116
+ eqns.push(generateDelay1Aux(v, context, subs, argDelay))
117
+
118
+ // Add the generated variables to the model
119
+ context.defineVariables(eqns)
113
120
  }
114
121
 
115
122
  /**
116
- * TODO: Docs
123
+ * Generate and define variables that are needed to implement a `DELAY1[I]` function call
124
+ * for a non-apply-to-all variable.
117
125
  */
118
126
  function generateDelay1VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit) {
119
127
  // XXX: This code that deals with separated variables is largely copied from the legacy
@@ -151,17 +159,23 @@ function generateDelay1VarsForSeparatedVar(v, context, subs, argInput, argDelay,
151
159
  const levelVarRefId = canonicalVensimName(levelLHS)
152
160
  Model.addNonAtoAVar(canonicalName(levelVarBaseName), [true])
153
161
 
162
+ // Generate the equation text for the delay level variable
163
+ const eqns = []
154
164
  // TODO: There should be parens around the factors in this init expression
155
165
  const argInitTimesDelay = `${argInit} * ${argDelay}`
156
- generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay)
166
+ eqns.push(generateDelayLevel(context, levelLHS, levelVarRefId, argInput, varLHS, argInitTimesDelay))
157
167
  v.delayVarRefId = levelVarRefId
158
168
 
159
169
  // Generate an aux variable to hold the delay time expression
160
- generateDelay1Aux(v, context, subs, argDelay)
170
+ eqns.push(generateDelay1Aux(v, context, subs, argDelay))
171
+
172
+ // Add the generated variables to the model
173
+ context.defineVariables(eqns)
161
174
  }
162
175
 
163
176
  /**
164
- * TODO: Docs
177
+ * Generate equation text for a single "delay time" aux variable that is used to implement
178
+ * a `DELAY1[I]` function call, and add a reference to the delay time variable.
165
179
  */
166
180
  function generateDelay1Aux(v, context, subs, argDelay) {
167
181
  const delayTimeVarName = newAuxVarName()
@@ -176,8 +190,8 @@ function generateDelay1Aux(v, context, subs, argDelay) {
176
190
  }
177
191
 
178
192
  const delayTimeEqn = `${delayTimeLHS} = ${argDelay} ~~|`
179
- context.defineVariable(delayTimeEqn)
180
193
  context.addVarReference(delayTimeVarRefId)
194
+ return delayTimeEqn
181
195
  }
182
196
 
183
197
  //
@@ -185,7 +199,8 @@ function generateDelay1Aux(v, context, subs, argDelay) {
185
199
  //
186
200
 
187
201
  /**
188
- * TODO: Docs
202
+ * Generate and define variables that are needed to implement a `DELAY3[I]` function call
203
+ * for a non-subscripted or apply-to-all variable.
189
204
  */
190
205
  function generateDelay3VarsForNormalVar(v, context, subs, argInput, argDelay, argInit) {
191
206
  // Generate names for the 3 level variables and 4 aux variables
@@ -208,28 +223,33 @@ function generateDelay3VarsForNormalVar(v, context, subs, argInput, argDelay, ar
208
223
  const level3RefId = canonicalName(level3)
209
224
 
210
225
  // Generate level variables
226
+ const eqns = []
211
227
  const delay3Val = `((${argDelay}) / 3)`
212
228
  // TODO: There should be parens around these factors
213
229
  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)
230
+ eqns.push(generateDelayLevel(context, level3LHS, level3RefId, aux2LHS, aux3LHS, initArg))
231
+ eqns.push(generateDelayLevel(context, level2LHS, level2RefId, aux1LHS, aux2LHS, initArg))
232
+ eqns.push(generateDelayLevel(context, level1LHS, level1RefId, argInput, aux1LHS, initArg))
217
233
  v.delayVarRefId = canonicalName(level3)
218
234
 
219
235
  // 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} ~~|`)
236
+ eqns.push(`${aux1LHS} = ${level1LHS} / ${delay3Val} ~~|`)
237
+ eqns.push(`${aux2LHS} = ${level2LHS} / ${delay3Val} ~~|`)
238
+ eqns.push(`${aux3LHS} = ${level3LHS} / ${delay3Val} ~~|`)
223
239
 
224
240
  // Generate an aux variable to hold the delay time expression
225
241
  v.delayTimeVarName = canonicalName(aux4)
226
242
  const delayTimeVarRefId = canonicalVensimName(aux4LHS)
227
- context.defineVariable(`${aux4LHS} = ${delay3Val} ~~|`)
243
+ eqns.push(`${aux4LHS} = ${delay3Val} ~~|`)
228
244
  context.addVarReference(delayTimeVarRefId)
245
+
246
+ // Add the generated variables to the model
247
+ context.defineVariables(eqns)
229
248
  }
230
249
 
231
250
  /**
232
- * TODO: Docs
251
+ * Generate and define variables that are needed to implement a `DELAY3[I]` function call
252
+ * for a non-apply-to-all variable.
233
253
  */
234
254
  function generateDelay3VarsForSeparatedVar(v, context, subs, argInput, argDelay, argInit) {
235
255
  // XXX: This code that deals with separated variables is largely copied from the legacy
@@ -290,23 +310,29 @@ function generateDelay3VarsForSeparatedVar(v, context, subs, argInput, argDelay,
290
310
  Model.addNonAtoAVar(canonicalName(level2), [true])
291
311
  Model.addNonAtoAVar(canonicalName(level3), [true])
292
312
 
313
+ Model.addNonAtoAVar(canonicalName(aux1), [true])
314
+ Model.addNonAtoAVar(canonicalName(aux2), [true])
315
+ Model.addNonAtoAVar(canonicalName(aux3), [true])
316
+
293
317
  // 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)
318
+ const eqns = []
319
+ eqns.push(generateDelayLevel(context, level3LHS, level3RefId, aux2LHS, aux3LHS, initArg))
320
+ eqns.push(generateDelayLevel(context, level2LHS, level2RefId, aux1LHS, aux2LHS, initArg))
321
+ eqns.push(generateDelayLevel(context, level1LHS, level1RefId, argInput, aux1LHS, initArg))
297
322
  v.delayVarRefId = level3RefId
298
323
 
299
324
  // 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} ~~|`)
325
+ eqns.push(`${aux1LHS} = ${level1LHS} / ${delay3Val} ~~|`)
326
+ eqns.push(`${aux2LHS} = ${level2LHS} / ${delay3Val} ~~|`)
327
+ eqns.push(`${aux3LHS} = ${level3LHS} / ${delay3Val} ~~|`)
303
328
 
304
329
  // Generate an aux variable to hold the delay time expression
305
330
  v.delayTimeVarName = canonicalName(aux4)
306
- if (isSeparatedVar(v)) {
307
- Model.addNonAtoAVar(v.delayTimeVarName, [true])
308
- }
331
+ Model.addNonAtoAVar(v.delayTimeVarName, [true])
309
332
  const delayTimeVarRefId = canonicalVensimName(aux4LHS)
310
- context.defineVariable(`${aux4LHS} = ${delay3Val} ~~|`)
333
+ eqns.push(`${aux4LHS} = ${delay3Val} ~~|`)
311
334
  context.addVarReference(delayTimeVarRefId)
335
+
336
+ // Add the generated variables to the model
337
+ context.defineVariables(eqns)
312
338
  }
@@ -33,9 +33,9 @@ export function generateGameVariables(v, callExpr, context) {
33
33
  }
34
34
 
35
35
  // Define a variable for the synthesized game inputs lookup
36
- const gameLookupVars = context.defineVariable(`${gameLookupVarName}${subs} ~~|`)
36
+ const gameLookupVars = context.defineVariables([`${gameLookupVarName}${subs} ~~|`])
37
37
 
38
- // Normally `defineVariable` sets `includeInOutput` to false for generated
38
+ // Normally `defineVariables` sets `includeInOutput` to false for generated
39
39
  // variables, but we want the generated lookup variable to appear in the
40
40
  // model listing so that the user can reference it, so set `includeInOutput`
41
41
  // to true. Also change the `varType` to 'lookup' instead of 'data'. We
@@ -26,6 +26,10 @@ export function generateNpvVariables(v, callExpr, context) {
26
26
  // const subs = this.genSubs(stream, discountRate, initVal, factor)
27
27
  const subs = ''
28
28
 
29
+ // Build an array of equation strings; variables will be defined for these at the
30
+ // end of this step once all equations are known
31
+ const eqns = []
32
+
29
33
  // Level 1:
30
34
  // df = INTEG((-df * discount rate) / (1 + discount rate * TIME STEP), 1)
31
35
  const dfVarName = newLevelVarName()
@@ -34,7 +38,7 @@ export function generateNpvVariables(v, callExpr, context) {
34
38
  // TODO: There should be parens around the `discount rate` argument in case it is an expression
35
39
  // and not a simple constant
36
40
  const dfEqn = `${dfLHS} = INTEG((-${dfLHS} * ${argDiscountRate}) / (1 + ${argDiscountRate} * TIME STEP), 1) ~~|`
37
- context.defineVariable(dfEqn)
41
+ eqns.push(dfEqn)
38
42
 
39
43
  // Level 2:
40
44
  // ncum = INTEG(stream * df, init val)
@@ -42,7 +46,7 @@ export function generateNpvVariables(v, callExpr, context) {
42
46
  const ncumVarId = canonicalName(ncumVarName)
43
47
  const ncumLHS = `${ncumVarName}${subs}`
44
48
  const ncumEqn = `${ncumLHS} = INTEG(${argStream} * ${dfLHS}, ${argInitVal}) ~~|`
45
- context.defineVariable(ncumEqn)
49
+ eqns.push(ncumEqn)
46
50
 
47
51
  // Aux:
48
52
  // npv = (ncum + stream * TIME STEP * df) * factor
@@ -52,7 +56,7 @@ export function generateNpvVariables(v, callExpr, context) {
52
56
  // TODO: There should be parens around the `stream` argument in case it is an expression
53
57
  // and not a simple constant
54
58
  const auxEqn = `${auxLHS} = (${ncumVarName} + ${argStream} * TIME STEP * ${dfVarName}) * ${argFactor} ~~|`
55
- context.defineVariable(auxEqn)
59
+ eqns.push(auxEqn)
56
60
  v.npvVarName = auxVarId
57
61
 
58
62
  // Add references to the generated variables
@@ -62,6 +66,9 @@ export function generateNpvVariables(v, callExpr, context) {
62
66
  context.addVarReference(dfVarId)
63
67
  context.addVarReference(auxVarId)
64
68
 
69
+ // Add the generated variables to the model
70
+ context.defineVariables(eqns)
71
+
65
72
  // TODO: Check on this comment from the legacy reader to see if it's still applicable:
66
73
  // If they have subscripts, the refIds are still just the var name, because they are apply-to-all arrays.
67
74
  }
@@ -45,17 +45,21 @@ export function generateSmoothVariables(v, callExpr, context) {
45
45
  const fnId = callExpr.fnId
46
46
  if (fnId === '_SMOOTH' || fnId === '_SMOOTHI') {
47
47
  // Generate 1 level variable that will replace the `SMOOTH[I]` function call
48
- const levelVarName = generateSmoothLevel(v, context, argInput, argDelay, argInit, 1)
48
+ const level = generateSmoothLevel(v, context, argInput, argDelay, argInit, 1)
49
49
  // For `SMOOTH[I]`, the smoothVarRefId is the level var's refId
50
- v.smoothVarRefId = levelVarName[0]
50
+ v.smoothVarRefId = level.varRefId
51
+ // Add the generated variable to the model
52
+ context.defineVariables([level.eqn])
51
53
  } else {
52
54
  // Generate 3 level variables that will replace the `SMOOTH3[I]` function call
53
55
  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)
56
+ const level1 = generateSmoothLevel(v, context, argInput, delay3Val, argInit, 1)
57
+ const level2 = generateSmoothLevel(v, context, level1.varFullName, delay3Val, argInit, 2)
58
+ const level3 = generateSmoothLevel(v, context, level2.varFullName, delay3Val, argInit, 3)
57
59
  // For `SMOOTH3[I]`, the smoothVarRefId is the final level var's refId
58
- v.smoothVarRefId = level3VarName[0]
60
+ v.smoothVarRefId = level3.varRefId
61
+ // Add the generated variables to the model
62
+ context.defineVariables([level1.eqn, level2.eqn, level3.eqn])
59
63
  }
60
64
  }
61
65
 
@@ -126,11 +130,14 @@ function generateSmoothLevel(v, context, argInput, argDelay, argInit, levelNumbe
126
130
  if (isSeparatedVar(v)) {
127
131
  Model.addNonAtoAVar(canonicalName(levelVarBaseName), [true])
128
132
  }
129
- context.defineVariable(levelEqn)
130
133
  context.addVarReference(levelVarRefId)
131
134
 
132
135
  // The name of the level variable returned here includes the original subscript/dimension names
133
136
  // (not the separated subscript names) so that the `argInput` is correct for the 2nd and 3rd levels
134
137
  const levelVarFullName = `${levelVarBaseName}${origSubs}`
135
- return [levelVarRefId, levelVarFullName]
138
+ return {
139
+ varRefId: levelVarRefId,
140
+ varFullName: levelVarFullName,
141
+ eqn: levelEqn
142
+ }
136
143
  }
@@ -24,12 +24,13 @@ export function generateTrendVariables(v, callExpr, context) {
24
24
  const subs = context.extractSubscriptsFromVarNames(argInput, argAvgTime, argInitVal)
25
25
 
26
26
  // Generate a level variable that will be used in place of the `TREND` call
27
+ const eqns = []
27
28
  const levelVarName = newLevelVarName()
28
29
  const levelLHS = `${levelVarName}${subs}`
29
30
  // TODO: There should be parens around the arguments in this equation in case any of them
30
31
  // is an expression and not a simple constant
31
32
  const levelEqn = `${levelLHS} = INTEG((${argInput} - ${levelLHS}) / ${argAvgTime}, ${argInput} / (1 + ${argInitVal} * ${argAvgTime})) ~~|`
32
- context.defineVariable(levelEqn)
33
+ eqns.push(levelEqn)
33
34
  context.addVarReference(canonicalName(levelVarName))
34
35
 
35
36
  // Generate a aux variable that will be used in place of the `TREND` call
@@ -38,7 +39,10 @@ export function generateTrendVariables(v, callExpr, context) {
38
39
  // TODO: There should be parens around the arguments in this equation in case any of them
39
40
  // is an expression and not a simple constant
40
41
  const auxEqn = `${auxLHS} = ZIDZ(${argInput} - ${levelLHS}, ${argAvgTime} * ABS(${levelLHS})) ~~|`
41
- context.defineVariable(auxEqn)
42
+ eqns.push(auxEqn)
42
43
  v.trendVarName = canonicalName(auxVarName)
43
44
  context.addVarReference(v.trendVarName)
45
+
46
+ // Add the generated variables to the model
47
+ context.defineVariables(eqns)
44
48
  }
@@ -23,7 +23,7 @@ export function generateLookup(v, callExpr, context) {
23
23
  const lookupVarName = newLookupVarName()
24
24
  const lookupDef = toPrettyString(lookupArg, { compact: true })
25
25
  const lookupEqn = `${lookupVarName}${lookupDef} ~~|`
26
- context.defineVariable(lookupEqn)
26
+ context.defineVariables([lookupEqn])
27
27
 
28
28
  // Keep track of all lookup variables that are referenced. This will be used later to decide
29
29
  // whether a lookup variable needs to be included in generated code.