@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.
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.20",
3
+ "version": "0.7.21",
4
4
  "description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "dependencies": {
8
- "@sdeverywhere/parse": "^0.1.1",
8
+ "@sdeverywhere/parse": "^0.1.2",
9
9
  "bufx": "^1.0.5",
10
10
  "byline": "^5.0.0",
11
11
  "csv-parse": "^5.3.3",
12
12
  "js-yaml": "^3.13.1",
13
13
  "ramda": "^0.27.0",
14
- "split-string": "^6.0.0",
15
14
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
16
15
  },
17
16
  "author": "Climate Interactive",
@@ -32,7 +31,7 @@
32
31
  "precommit": "../../scripts/precommit",
33
32
  "type-check": "tsc --noEmit -p tsconfig-test.json",
34
33
  "test": "vitest run",
35
- "test:watch": "vitest",
34
+ "test:watch": "vitest --hideSkippedTests",
36
35
  "test:ci": "vitest run",
37
36
  "ci:build": "run-s lint prettier:check type-check test:ci"
38
37
  }
@@ -3,9 +3,10 @@ import util from 'util'
3
3
  import B from 'bufx'
4
4
  import { parse as parseCsv } from 'csv-parse/sync'
5
5
  import * as R from 'ramda'
6
- import split from 'split-string'
7
6
  import XLSX from 'xlsx'
8
7
 
8
+ import { canonicalId, canonicalVarId } from '@sdeverywhere/parse'
9
+
9
10
  // Set true to print a stack trace in vlog
10
11
  export const PRINT_VLOG_TRACE = false
11
12
 
@@ -40,25 +41,11 @@ export function resetHelperState() {
40
41
  }
41
42
 
42
43
  export let canonicalName = name => {
43
- // Format a model variable name into a valid C identifier.
44
- return (
45
- '_' +
46
- name
47
- .trim()
48
- .replace(/"/g, '_')
49
- .replace(/\s+!$/g, '!')
50
- .replace(/\s/g, '_')
51
- .replace(/,/g, '_')
52
- .replace(/-/g, '_')
53
- .replace(/\./g, '_')
54
- .replace(/\$/g, '_')
55
- .replace(/'/g, '_')
56
- .replace(/&/g, '_')
57
- .replace(/%/g, '_')
58
- .replace(/\//g, '_')
59
- .replace(/\|/g, '_')
60
- .toLowerCase()
61
- )
44
+ // Format a model variable or subscript/dimension name into a valid C identifier.
45
+ // In the case where you have a full variable name that includes subscripts/dimensions
46
+ // (e.g., 'Variable name[DimA,B2]'), use `canonicalVensimName` to convert the
47
+ // base variable name and subscript/dimension parts to canonical form indepdendently.
48
+ return canonicalId(name)
62
49
  }
63
50
  export let decanonicalize = name => {
64
51
  // Decanonicalize the var name.
@@ -71,9 +58,6 @@ export let decanonicalize = name => {
71
58
  }
72
59
  return name
73
60
  }
74
- export let cFunctionName = name => {
75
- return canonicalName(name).toUpperCase()
76
- }
77
61
  export let isSeparatedVar = v => {
78
62
  return v.separationDims.length > 0
79
63
  }
@@ -244,22 +228,7 @@ export let readCsv = (pathname, delimiter = ',') => {
244
228
  }
245
229
  // Convert the var name and subscript names to canonical form separately.
246
230
  export let canonicalVensimName = vname => {
247
- let result = vname
248
- let m = vname.match(/([^[]+)(?:\[([^\]]+)\])?/)
249
- if (m) {
250
- result = canonicalName(m[1])
251
- if (m[2]) {
252
- let subscripts = m[2].split(',').map(x => canonicalName(x))
253
- result += `[${subscripts.join(',')}]`
254
- }
255
- }
256
- return result
257
- }
258
- // Split a model string into an array of equations without the "|" terminator.
259
- // Allow "|" to occur in quoted variable names across line breaks.
260
- // Retain the backslash character.
261
- export let splitEquations = mdl => {
262
- return split(mdl, { separator: '|', quotes: ['"'], keep: () => true })
231
+ return canonicalVarId(vname)
263
232
  }
264
233
  // Function to map over lists's value and index
265
234
  export let mapIndexed = R.addIndex(R.map)
@@ -2,7 +2,7 @@ import util from 'util'
2
2
  import B from 'bufx'
3
3
  import yaml from 'js-yaml'
4
4
  import * as R from 'ramda'
5
- import { canonicalName, asort, vlog } from './helpers.js'
5
+ import { canonicalName, vlog } from './helpers.js'
6
6
 
7
7
  // A subscript is a dimension or an index.
8
8
  // Both have the same properties: model name, canonical name, family, values.
@@ -233,18 +233,6 @@ export function loadSubscriptsFromYaml(yamlSubs) {
233
233
  subscripts.set(k, subs[k])
234
234
  }
235
235
  }
236
- export function normalizeSubscripts(subscripts) {
237
- // Sort a list of subscript names already in canonical form according to the subscript family.
238
- let subs = R.map(name => sub(name), subscripts)
239
- subs = R.sortBy(R.prop('family'), subs)
240
- let normalizedSubs
241
- try {
242
- normalizedSubs = R.map(R.prop('name'), subs)
243
- } catch (e) {
244
- console.error(`normalizeSubscripts fails for ${subscripts}`)
245
- }
246
- return normalizedSubs
247
- }
248
236
  export function extractMarkedDims(subscripts) {
249
237
  // Extract all marked dimensions and update subscripts.
250
238
  let dims = []
@@ -366,13 +354,3 @@ export function separatedVariableIndex(rhsSub, variable, rhsSubscripts) {
366
354
  }
367
355
  return null
368
356
  }
369
- // Function to filter canonical dimension names from a list of names
370
- export let dimensionNames = R.pipe(
371
- R.filter(subscript => isDimension(subscript)),
372
- asort
373
- )
374
- // Function to filter canonical index names from a list of names
375
- export let indexNames = R.pipe(
376
- R.filter(subscript => isIndex(subscript)),
377
- asort
378
- )
@@ -1,5 +1,5 @@
1
1
  import { cartesianProductOf, cdbl } from '../_shared/helpers.js'
2
- import { isDimension, normalizeSubscripts, sub } from '../_shared/subscript.js'
2
+ import { isDimension, sub } from '../_shared/subscript.js'
3
3
 
4
4
  /**
5
5
  * Generate code for a single element in a const list definition.
@@ -12,12 +12,10 @@ import { isDimension, normalizeSubscripts, sub } from '../_shared/subscript.js'
12
12
  export function generateConstListElement(variable, parsedEqn) {
13
13
  // In the "read variables" phase, const lists are expanded into separated variable
14
14
  // definitions, so `variable` here will have `subscripts` that represent specific
15
- // subscript indices in normalized order (alphabetized by parent dimension/family
16
- // name). However, we need to consult the LHS subscripts/dimensions, which will
17
- // be in the original order from the model equation.
15
+ // subscript indices that are ordered according to the dimension positions from the
16
+ // variable definition.
18
17
  //
19
- // In the following example,
20
- // we have a 2D variable whose original dimensions are not in normal order:
18
+ // In the following example, we have a 2D variable whose original dimensions are:
21
19
  // DimA: A1, A2 ~~|
22
20
  // DimB: B1, B2, B3 ~~|
23
21
  // x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~|
@@ -53,25 +51,17 @@ export function generateConstListElement(variable, parsedEqn) {
53
51
  // order of the dimensions from the equation LHS.
54
52
  const origCombos = cartesianProductOf(subIdArrays)
55
53
 
56
- // Now we have the combinations in original order:
54
+ // Convert to strings to make matching easier. Now we have the combinations in
55
+ // original order.
57
56
  // [_b1,_a1]
58
57
  // [_b1,_a2]
59
58
  // [_b2,_a1]
60
59
  // ...
61
- // But we need to put them into normalized order so that we can find the index of
62
- // `variable.subscripts` (which is already in normalized order).
63
- const normalizedCombos = origCombos.map(normalizeSubscripts)
64
-
65
- // Convert to strings to make matching easier. Now we have the strings in normalized order:
66
- // [_a1,_b1]
67
- // [_a2,_b1]
68
- // [_a1,_b2]
69
- // ...
70
- const comboStrings = normalizedCombos.map(combo => combo.map(subId => `[${subId}]`).join(''))
60
+ const comboStrings = origCombos.map(combo => combo.map(subId => `[${subId}]`).join(''))
71
61
 
72
62
  // Convert `variable.subscripts` into the same format so that we can do an array lookup,
73
- // for example if this separated variable instance is x[_a2,_b1], this will be:
74
- // [_a2,_b1]
63
+ // for example if this separated variable instance is x[_b1,_a2], this will be:
64
+ // [_b1,_a2]
75
65
  const lhsComboString = variable.subscripts.map(subId => `[${subId}]`).join('')
76
66
 
77
67
  // Find the index of the combination that matches `variable.subscripts`
@@ -1,13 +1,4 @@
1
- import {
2
- dimensionNames,
3
- hasMapping,
4
- isDimension,
5
- isIndex,
6
- isTrivialDimension,
7
- normalizeSubscripts,
8
- separatedVariableIndex,
9
- sub
10
- } from '../_shared/subscript.js'
1
+ import { hasMapping, isDimension, isIndex, isTrivialDimension, sub } from '../_shared/subscript.js'
11
2
  import { generateConstListElement } from './gen-const-list.js'
12
3
 
13
4
  import { generateDirectConstInit } from './gen-direct-const.js'
@@ -18,6 +9,9 @@ import { generateLookupFromPoints } from './gen-lookup-from-points.js'
18
9
 
19
10
  import LoopIndexVars from './loop-index-vars.js'
20
11
 
12
+ const loopIndexVarNames = ['i', 'j', 'k', 'l', 'm']
13
+ const arrayIndexVarNames = ['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r']
14
+
21
15
  /**
22
16
  * Generate C code for the given model equation.
23
17
  *
@@ -36,8 +30,22 @@ import LoopIndexVars from './loop-index-vars.js'
36
30
  */
37
31
  export function generateEquation(variable, mode, extData, directData, modelDir, outFormat) {
38
32
  // Maps of LHS subscript families to loop index vars for lookup on the RHS
39
- const loopIndexVars = new LoopIndexVars(['i', 'j', 'k', 'l', 'm'])
40
- const arrayIndexVars = new LoopIndexVars(['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r'])
33
+ const loopIndexVars = new LoopIndexVars(loopIndexVarNames)
34
+ const arrayIndexVars = new LoopIndexVars(arrayIndexVarNames)
35
+
36
+ // Make the generated loops easier to follow by eagerly determining the order of index
37
+ // variables based on the order of the LHS dimension names. For example, if we have:
38
+ // x[DimA, DimC, DimB] = y[DimB, DimC, DimA] ~~|
39
+ // This will generate loop index variable mappings in the following order:
40
+ // DimA -> i
41
+ // DimC -> j
42
+ // DimB -> k
43
+ const lhsDimIds = variable.subscripts.filter(isDimension)
44
+ for (const lhsDimId of lhsDimIds) {
45
+ // We ignore the return value here. Calling `index` will make `LoopIndexVars` eagerly
46
+ // set up a mapping from the dimension ID to the loop index variable name.
47
+ loopIndexVars.index(lhsDimId)
48
+ }
41
49
 
42
50
  // Generate the LHS variable reference code
43
51
  const parsedEqn = variable.parsedEqn
@@ -64,17 +72,14 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
64
72
  return [comment, ...initCode]
65
73
  }
66
74
 
67
- // Get the dimension IDs for the LHS variable
68
- const dimIds = dimensionNames(variable.subscripts)
69
-
70
75
  // Turn each dimension ID into a loop with a loop index variable.
71
76
  // If the variable has no subscripts, nothing will be emitted here.
72
77
  const indexDecl = outFormat === 'js' ? 'let' : 'size_t'
73
78
  const openLoops = []
74
79
  const closeLoops = []
75
- for (const dimId of dimIds) {
76
- const indexName = loopIndexVars.index(dimId)
77
- const dimLength = sub(dimId).size
80
+ for (const lhsDimId of lhsDimIds) {
81
+ const indexName = loopIndexVars.index(lhsDimId)
82
+ const dimLength = sub(lhsDimId).size
78
83
  openLoops.push(` for (${indexDecl} ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
79
84
  closeLoops.push(' }')
80
85
  }
@@ -127,9 +132,6 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
127
132
  // Keep a buffer of code that will be included after the generated primary formula
128
133
  const postFormulaLines = []
129
134
 
130
- // Keep track of marked dimensions
131
- const markedDimIds = new Set()
132
-
133
135
  // Generate code for an equation with an expression on the RHS
134
136
  const genExprCtx = {
135
137
  variable,
@@ -138,14 +140,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
138
140
  cLhs,
139
141
  loopIndexVars,
140
142
  arrayIndexVars,
141
- resetMarkedDims: () => markedDimIds.clear(),
142
- addMarkedDim: dimId => markedDimIds.add(dimId),
143
143
  emitPreInnerLoop: s => preInnerLoopLines.push(s),
144
144
  emitPreFormula: s => preFormulaLines.push(s),
145
145
  emitPostFormula: s => postFormulaLines.push(s),
146
- cVarRef: varRef => cVarRef(variable, varRef, markedDimIds, loopIndexVars, arrayIndexVars),
146
+ cVarRef: varRef => cVarRef(variable, varRef, loopIndexVars, arrayIndexVars),
147
147
  cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
148
- cVarIndex: subOrDimId => cVarIndex(variable, [subOrDimId], subOrDimId, markedDimIds, loopIndexVars, arrayIndexVars)
148
+ cVarIndex: subOrDimId => cVarIndex(variable, subOrDimId, loopIndexVars, arrayIndexVars)
149
149
  }
150
150
  const cRhs = generateExpr(parsedEqn.rhs.expr, genExprCtx)
151
151
  const formula = ` ${cLhs} = ${cRhs};`
@@ -177,9 +177,12 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
177
177
  // When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
178
178
  return `[${i}]`
179
179
  } else {
180
- return `[${subId}][${i}]`
180
+ // Otherwise, emit e.g. `[_dim[i]]`
181
+ return `[${subId}[${i}]]`
181
182
  }
182
183
  } else {
184
+ // This is a specific subscript (i.e., an index); dereference the array using the index
185
+ // number of the subscript
183
186
  return `[${sub(subId).value}]`
184
187
  }
185
188
  })
@@ -191,33 +194,26 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
191
194
  *
192
195
  * @param {*} lhsVariable The LHS `Variable` instance.
193
196
  * @param {*} rhsVarRef The `VariableRef` used in a RHS expression.
194
- * @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
195
- * in an array function, for example `SUM(x[DimA!])`.
196
197
  * @param {LoopIndexVars} loopIndexVars The loop index state.
197
198
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
198
199
  * (that use marked dimensions).
199
200
  * @returns {string} The C variable reference.
200
201
  */
201
- function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndexVars) {
202
+ function cVarRef(lhsVariable, rhsVarRef, loopIndexVars, arrayIndexVars) {
202
203
  if (rhsVarRef.subscriptRefs === undefined) {
203
204
  // No subscripts, so return the base variable ID
204
205
  return rhsVarRef.varId
205
206
  }
206
207
 
207
- // Normalize the RHS subscripts
208
- let rhsSubIds
209
- try {
210
- // XXX: For now, strip the mark here (need to revisit this)
211
- rhsSubIds = normalizeSubscripts(rhsVarRef.subscriptRefs.map(subRef => subRef.subId.replace('!', '')))
212
- } catch (e) {
213
- throw new Error(`normalizeSubscripts failed in rhsVarRef: refId=${lhsVariable.refId} error=${e}`)
214
- }
208
+ // Get the RHS subscript IDs. Note that we leave the "!" in place in the case of
209
+ // marked dimensions; they will be handled specially in `cVarIndex`.
210
+ const rhsSubIds = rhsVarRef.subscriptRefs.map(subRef => subRef.subId)
215
211
 
216
212
  // Determine the subscript code (array lookup) for each dimension. For example, if
217
213
  // the RHS variable reference in the model looks like `x[DimA]`, this will convert the
218
214
  // `[DimA]` part to `[_dima[i]]` (or simply `[i]` if it is a "trivial" dimension).
219
215
  const cSubParts = rhsSubIds.map(rhsSubId => {
220
- return cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars)
216
+ return cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars)
221
217
  })
222
218
 
223
219
  return `${rhsVarRef.varId}${cSubParts.map(part => `[${part}]`).join('')}`
@@ -227,57 +223,97 @@ function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndex
227
223
  * Return the C code for indexing into a subscripted variable.
228
224
  *
229
225
  * @param {*} lhsVariable The LHS `Variable` instance.
230
- * @param {string[]} rhsSubIds The set of all subscript or dimension IDs used on the RHS.
231
226
  * @param {string} rhsSubId The specific subscript or dimension ID being evaluated.
232
- * @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
233
- * in an array function, for example `SUM(x[DimA!])`.
234
227
  * @param {LoopIndexVars} loopIndexVars The loop index state.
235
228
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
236
229
  * (that use marked dimensions).
237
230
  * @returns {string} The C variable reference.
238
231
  */
239
- function cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars) {
232
+ function cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars) {
233
+ // NOTE: The code in this function follows basically the same steps that are used in
234
+ // the `resolveRhsSubOrDim` function in the `readEquation` phase.
235
+ // TODO: Since these two functions are so similar, it would be better to write a
236
+ // single function that has the common logic, and then write a wrapper function
237
+ // that converts the resulting subscript or dimension ID to an index
238
+
239
+ // Helper function that returns either `indexVarName` or `dimId[indexVarName]` depending
240
+ // on whether `dimId` is considered "trivial".
241
+ function optimalIndex(indexVars, dimId) {
242
+ const indexVarName = indexVars.index(dimId)
243
+ if (isTrivialDimension(dimId)) {
244
+ // When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
245
+ return `${indexVarName}`
246
+ } else {
247
+ // Otherwise, emit e.g. `[_dim[i]]`
248
+ return `${dimId}[${indexVarName}]`
249
+ }
250
+ }
251
+
252
+ if (rhsSubId.endsWith('!')) {
253
+ // The dimension ID at this position is "marked", indicating that the vector function
254
+ // (e.g., `SUM`) should operate over the elements in this dimension. Strip the "!"
255
+ // to get the actual dimension ID, then get the associated array loop index variable.
256
+ const rhsDimId = rhsSubId.replace('!', '')
257
+ return optimalIndex(arrayIndexVars, rhsDimId)
258
+ }
259
+
240
260
  if (isIndex(rhsSubId)) {
241
261
  // This is a specific subscript (i.e., an index); dereference the array using the index
242
262
  // number of the subscript
243
263
  return `${sub(rhsSubId).value}`
244
264
  }
245
265
 
246
- // Otherwise, this is a dimension. Get the corresponding loop index variable used
247
- // in the "for" loop.
248
- let indexName
249
- if (markedDimIds.has(rhsSubId)) {
250
- // This is a marked dimension as used in an array function (e.g., `SUM`), so use
251
- // the name of the array loop index variable
252
- indexName = arrayIndexVars.index(rhsSubId)
253
- } else {
254
- // Use the single index name for a separated variable if it exists
255
- const separatedIndexName = separatedVariableIndex(rhsSubId, lhsVariable, rhsSubIds)
256
- if (separatedIndexName) {
257
- return `${sub(separatedIndexName).value}`
266
+ // At this point we know that it is a dimension ID. Figure out which LHS subscript or
267
+ // dimension is a match. First see if there is an exact match.
268
+ const lhsSubRefs = lhsVariable.parsedEqn.lhs.varDef.subscriptRefs
269
+ const lhsSubIds = lhsSubRefs?.map(subRef => subRef.subId) || []
270
+ const lhsDimIndex = lhsSubIds.findIndex(lhsSubId => lhsSubId === rhsSubId)
271
+ if (lhsDimIndex >= 0) {
272
+ // There is a match. If the LHS variable is separated, use the separated subscript
273
+ // ID at this position (i.e., the value from the `subscripts` array), otherwise we
274
+ // use the dimension ID at this position.
275
+ const lhsSubOrDimId = lhsVariable.subscripts[lhsDimIndex]
276
+ if (isIndex(lhsSubOrDimId)) {
277
+ // This is a specific subscript (i.e., an index); dereference the array using the index
278
+ // number of the subscript
279
+ return `${sub(lhsSubOrDimId).value}`
280
+ } else {
281
+ // This is a dimension; use the associated loop index variable
282
+ return optimalIndex(loopIndexVars, lhsSubOrDimId)
258
283
  }
284
+ }
259
285
 
260
- // See if we need to apply a mapping because the RHS dim is not found on the LHS
261
- const found = lhsVariable.subscripts.findIndex(lhsSubId => sub(lhsSubId).family === sub(rhsSubId).family)
262
- if (found < 0) {
263
- // Find the mapping from the RHS subscript to a LHS subscript
264
- for (const lhsSubId of lhsVariable.subscripts) {
265
- if (hasMapping(rhsSubId, lhsSubId)) {
266
- indexName = loopIndexVars.index(lhsSubId)
267
- return `__map${rhsSubId}${lhsSubId}[${indexName}]`
268
- }
286
+ // There wasn't an exact match by dimension ID. Find the position of the LHS dimension
287
+ // that has a mapping to the RHS dimension.
288
+ const mappedLhsDimIndex = lhsSubIds.findIndex(lhsSubId => hasMapping(rhsSubId, lhsSubId))
289
+ if (mappedLhsDimIndex >= 0) {
290
+ // There is a match. If the LHS variable is separated, use the _mapped_ separated
291
+ // subscript ID at this position (i.e., the value from the `subscripts` array),
292
+ // otherwise we use the _mapped_ dimension ID at this position.
293
+ const mappedLhsSubOrDimId = lhsVariable.subscripts[mappedLhsDimIndex]
294
+ if (isIndex(mappedLhsSubOrDimId)) {
295
+ // This is a specific subscript (i.e., an index); dereference the array using the index
296
+ // number of the _mapped_ subscript
297
+ const mappedLhsSubId = mappedLhsSubOrDimId
298
+ const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
299
+ const lhsDim = sub(mappedLhsDimId)
300
+ const rhsDim = sub(rhsSubId)
301
+ const lhsSubIndex = lhsDim.value.indexOf(mappedLhsSubId)
302
+ if (lhsSubIndex >= 0) {
303
+ const mappedSubId = rhsDim.mappings[mappedLhsDimId][lhsSubIndex]
304
+ return `${sub(mappedSubId).value}`
305
+ } else {
306
+ throw new Error(
307
+ `Failed to find mapped LHS subscript ${mappedLhsSubId} for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`
308
+ )
269
309
  }
310
+ } else {
311
+ // Determine the dimension mapping
312
+ const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
313
+ const indexVarName = loopIndexVars.index(mappedLhsDimId)
314
+ return `__map${rhsSubId}${mappedLhsDimId}[${indexVarName}]`
270
315
  }
271
-
272
- // There is no mapping, so use the loop index for this dim family on the LHS
273
- indexName = loopIndexVars.index(rhsSubId)
274
- }
275
-
276
- // Dereference the array using the corresponding loop index variable
277
- if (isTrivialDimension(rhsSubId)) {
278
- // When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
279
- return `${indexName}`
280
316
  } else {
281
- return `${rhsSubId}[${indexName}]`
317
+ throw new Error(`Failed to find LHS dimension for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`)
282
318
  }
283
319
  }
@@ -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
+ }