@sdeverywhere/compile 0.7.20 → 0.7.21

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,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.