@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.
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.20",
3
+ "version": "0.7.22",
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
- )
@@ -153,11 +153,19 @@ ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}`
153
153
  let setLookupBody
154
154
  if (spec.customLookups === true || Array.isArray(spec.customLookups)) {
155
155
  setLookupBody = `\
156
+ Lookup** pLookup = NULL;
156
157
  switch (varIndex) {
157
158
  ${setLookupImpl(Model.varIndexInfo(), spec.customLookups)}
158
159
  default:
159
160
  fprintf(stderr, "No lookup found for var index %zu in setLookup\\n", varIndex);
160
161
  break;
162
+ }
163
+ if (pLookup != NULL) {
164
+ if (*pLookup == NULL) {
165
+ *pLookup = __new_lookup(numPoints, /*copy=*/true, points);
166
+ } else {
167
+ __set_lookup(*pLookup, numPoints, points);
168
+ }
161
169
  }`
162
170
  } else {
163
171
  let msg = 'The setLookup function was not enabled for the generated model. '
@@ -200,19 +208,6 @@ void setInputsFromBuffer(double* inputData) {
200
208
  ${inputsFromBufferImpl()}
201
209
  }
202
210
 
203
- void replaceLookup(Lookup** lookup, double* points, size_t numPoints) {
204
- if (lookup == NULL) {
205
- return;
206
- }
207
- if (*lookup != NULL) {
208
- __delete_lookup(*lookup);
209
- *lookup = NULL;
210
- }
211
- if (points != NULL) {
212
- *lookup = __new_lookup(numPoints, /*copy=*/true, points);
213
- }
214
- }
215
-
216
211
  void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints) {
217
212
  ${setLookupBody}
218
213
  }
@@ -441,7 +436,7 @@ ${section(chunk)}
441
436
  return inputVars.join('\n')
442
437
  }
443
438
  function setLookupImpl(varIndexInfo, customLookups) {
444
- // Emit `replaceLookup` calls for all lookups and data variables that can be overridden
439
+ // Emit case statements for all lookups and data variables that can be overridden
445
440
  // at runtime
446
441
  let includeCase
447
442
  if (Array.isArray(customLookups)) {
@@ -467,7 +462,7 @@ ${section(chunk)}
467
462
  }
468
463
  let c = ''
469
464
  c += ` case ${info.varIndex}:\n`
470
- c += ` replaceLookup(&${lookupVar}, points, numPoints);\n`
465
+ c += ` pLookup = &${lookupVar};\n`
471
466
  c += ` break;`
472
467
  return c
473
468
  })
@@ -251,10 +251,15 @@ ${chunkedFunctions('evalLevels', true, Model.levelVars(), ' // Evaluate levels'
251
251
  }
252
252
  const varIndex = varSpec.varIndex;
253
253
  const subs = varSpec.subscriptIndices;
254
+ let lookup;
254
255
  switch (varIndex) {
255
256
  ${setLookupImpl(Model.varIndexInfo(), spec.customLookups)}
256
257
  default:
257
258
  throw new Error(\`No lookup found for var index \${varIndex} in setLookup\`);
259
+ }
260
+ if (lookup) {
261
+ const size = points ? points.length / 2 : 0;
262
+ lookup.setData(size, points);
258
263
  }`
259
264
  } else {
260
265
  let msg = 'The setLookup function was not enabled for the generated model. '
@@ -307,7 +312,7 @@ ${customOutputSection(Model.varIndexInfo(), spec.customOutputs)}
307
312
  return `\
308
313
  /*export*/ function setInputs(valueAtIndex /*: (index: number) => number*/) {${inputsFromBufferImpl()}}
309
314
 
310
- /*export*/ function setLookup(varSpec /*: VarSpec*/, points /*: Float64Array*/) {
315
+ /*export*/ function setLookup(varSpec /*: VarSpec*/, points /*: Float64Array | undefined*/) {
311
316
  ${setLookupBody}
312
317
  }
313
318
 
@@ -517,7 +522,7 @@ ${section(chunk)}
517
522
  return inputVars
518
523
  }
519
524
  function setLookupImpl(varIndexInfo, customLookups) {
520
- // Emit `createLookup` calls for all lookups and data variables that can be overridden
525
+ // Emit case statements for all lookups and data variables that can be overridden
521
526
  // at runtime
522
527
  let overrideAllowed
523
528
  if (Array.isArray(customLookups)) {
@@ -543,7 +548,7 @@ ${section(chunk)}
543
548
  }
544
549
  let c = ''
545
550
  c += ` case ${info.varIndex}:\n`
546
- c += ` ${lookupVar} = fns.createLookup(points.length / 2, points);\n`
551
+ c += ` lookup = ${lookupVar};\n`
547
552
  c += ` break;`
548
553
  return c
549
554
  })
@@ -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
  }
@@ -108,13 +113,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
108
113
  // Apply special handling for lookup variables. The data for lookup variables is already
109
114
  // defined as a set of explicit data points (stored in the `Variable` instance).
110
115
  if (variable.isLookup()) {
111
- if (variable.varSubtype === 'gameInputs') {
112
- // For a synthesized game inputs lookup, there is no data array (the data is expected
113
- // to be supplied at runtime), so don't emit decl or init code for these
114
- return []
116
+ // Emit decl/init code for the lookup
117
+ const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
118
+ if (lookupDef.length > 0) {
119
+ return [...openLoops, ...lookupDef, ...closeLoops]
115
120
  } else {
116
- // For all other lookups, emit decl/init code for the lookup
117
- return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
121
+ return []
118
122
  }
119
123
  }
120
124
 
@@ -127,9 +131,6 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
127
131
  // Keep a buffer of code that will be included after the generated primary formula
128
132
  const postFormulaLines = []
129
133
 
130
- // Keep track of marked dimensions
131
- const markedDimIds = new Set()
132
-
133
134
  // Generate code for an equation with an expression on the RHS
134
135
  const genExprCtx = {
135
136
  variable,
@@ -138,14 +139,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
138
139
  cLhs,
139
140
  loopIndexVars,
140
141
  arrayIndexVars,
141
- resetMarkedDims: () => markedDimIds.clear(),
142
- addMarkedDim: dimId => markedDimIds.add(dimId),
143
142
  emitPreInnerLoop: s => preInnerLoopLines.push(s),
144
143
  emitPreFormula: s => preFormulaLines.push(s),
145
144
  emitPostFormula: s => postFormulaLines.push(s),
146
- cVarRef: varRef => cVarRef(variable, varRef, markedDimIds, loopIndexVars, arrayIndexVars),
145
+ cVarRef: varRef => cVarRef(variable, varRef, loopIndexVars, arrayIndexVars),
147
146
  cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
148
- cVarIndex: subOrDimId => cVarIndex(variable, [subOrDimId], subOrDimId, markedDimIds, loopIndexVars, arrayIndexVars)
147
+ cVarIndex: subOrDimId => cVarIndex(variable, subOrDimId, loopIndexVars, arrayIndexVars)
149
148
  }
150
149
  const cRhs = generateExpr(parsedEqn.rhs.expr, genExprCtx)
151
150
  const formula = ` ${cLhs} = ${cRhs};`
@@ -177,9 +176,12 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
177
176
  // When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
178
177
  return `[${i}]`
179
178
  } else {
180
- return `[${subId}][${i}]`
179
+ // Otherwise, emit e.g. `[_dim[i]]`
180
+ return `[${subId}[${i}]]`
181
181
  }
182
182
  } else {
183
+ // This is a specific subscript (i.e., an index); dereference the array using the index
184
+ // number of the subscript
183
185
  return `[${sub(subId).value}]`
184
186
  }
185
187
  })
@@ -191,33 +193,26 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
191
193
  *
192
194
  * @param {*} lhsVariable The LHS `Variable` instance.
193
195
  * @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
196
  * @param {LoopIndexVars} loopIndexVars The loop index state.
197
197
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
198
198
  * (that use marked dimensions).
199
199
  * @returns {string} The C variable reference.
200
200
  */
201
- function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndexVars) {
201
+ function cVarRef(lhsVariable, rhsVarRef, loopIndexVars, arrayIndexVars) {
202
202
  if (rhsVarRef.subscriptRefs === undefined) {
203
203
  // No subscripts, so return the base variable ID
204
204
  return rhsVarRef.varId
205
205
  }
206
206
 
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
- }
207
+ // Get the RHS subscript IDs. Note that we leave the "!" in place in the case of
208
+ // marked dimensions; they will be handled specially in `cVarIndex`.
209
+ const rhsSubIds = rhsVarRef.subscriptRefs.map(subRef => subRef.subId)
215
210
 
216
211
  // Determine the subscript code (array lookup) for each dimension. For example, if
217
212
  // the RHS variable reference in the model looks like `x[DimA]`, this will convert the
218
213
  // `[DimA]` part to `[_dima[i]]` (or simply `[i]` if it is a "trivial" dimension).
219
214
  const cSubParts = rhsSubIds.map(rhsSubId => {
220
- return cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars)
215
+ return cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars)
221
216
  })
222
217
 
223
218
  return `${rhsVarRef.varId}${cSubParts.map(part => `[${part}]`).join('')}`
@@ -227,57 +222,97 @@ function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndex
227
222
  * Return the C code for indexing into a subscripted variable.
228
223
  *
229
224
  * @param {*} lhsVariable The LHS `Variable` instance.
230
- * @param {string[]} rhsSubIds The set of all subscript or dimension IDs used on the RHS.
231
225
  * @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
226
  * @param {LoopIndexVars} loopIndexVars The loop index state.
235
227
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
236
228
  * (that use marked dimensions).
237
229
  * @returns {string} The C variable reference.
238
230
  */
239
- function cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars) {
231
+ function cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars) {
232
+ // NOTE: The code in this function follows basically the same steps that are used in
233
+ // the `resolveRhsSubOrDim` function in the `readEquation` phase.
234
+ // TODO: Since these two functions are so similar, it would be better to write a
235
+ // single function that has the common logic, and then write a wrapper function
236
+ // that converts the resulting subscript or dimension ID to an index
237
+
238
+ // Helper function that returns either `indexVarName` or `dimId[indexVarName]` depending
239
+ // on whether `dimId` is considered "trivial".
240
+ function optimalIndex(indexVars, dimId) {
241
+ const indexVarName = indexVars.index(dimId)
242
+ if (isTrivialDimension(dimId)) {
243
+ // When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
244
+ return `${indexVarName}`
245
+ } else {
246
+ // Otherwise, emit e.g. `[_dim[i]]`
247
+ return `${dimId}[${indexVarName}]`
248
+ }
249
+ }
250
+
251
+ if (rhsSubId.endsWith('!')) {
252
+ // The dimension ID at this position is "marked", indicating that the vector function
253
+ // (e.g., `SUM`) should operate over the elements in this dimension. Strip the "!"
254
+ // to get the actual dimension ID, then get the associated array loop index variable.
255
+ const rhsDimId = rhsSubId.replace('!', '')
256
+ return optimalIndex(arrayIndexVars, rhsDimId)
257
+ }
258
+
240
259
  if (isIndex(rhsSubId)) {
241
260
  // This is a specific subscript (i.e., an index); dereference the array using the index
242
261
  // number of the subscript
243
262
  return `${sub(rhsSubId).value}`
244
263
  }
245
264
 
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}`
265
+ // At this point we know that it is a dimension ID. Figure out which LHS subscript or
266
+ // dimension is a match. First see if there is an exact match.
267
+ const lhsSubRefs = lhsVariable.parsedEqn.lhs.varDef.subscriptRefs
268
+ const lhsSubIds = lhsSubRefs?.map(subRef => subRef.subId) || []
269
+ const lhsDimIndex = lhsSubIds.findIndex(lhsSubId => lhsSubId === rhsSubId)
270
+ if (lhsDimIndex >= 0) {
271
+ // There is a match. If the LHS variable is separated, use the separated subscript
272
+ // ID at this position (i.e., the value from the `subscripts` array), otherwise we
273
+ // use the dimension ID at this position.
274
+ const lhsSubOrDimId = lhsVariable.subscripts[lhsDimIndex]
275
+ if (isIndex(lhsSubOrDimId)) {
276
+ // This is a specific subscript (i.e., an index); dereference the array using the index
277
+ // number of the subscript
278
+ return `${sub(lhsSubOrDimId).value}`
279
+ } else {
280
+ // This is a dimension; use the associated loop index variable
281
+ return optimalIndex(loopIndexVars, lhsSubOrDimId)
258
282
  }
283
+ }
259
284
 
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
- }
285
+ // There wasn't an exact match by dimension ID. Find the position of the LHS dimension
286
+ // that has a mapping to the RHS dimension.
287
+ const mappedLhsDimIndex = lhsSubIds.findIndex(lhsSubId => hasMapping(rhsSubId, lhsSubId))
288
+ if (mappedLhsDimIndex >= 0) {
289
+ // There is a match. If the LHS variable is separated, use the _mapped_ separated
290
+ // subscript ID at this position (i.e., the value from the `subscripts` array),
291
+ // otherwise we use the _mapped_ dimension ID at this position.
292
+ const mappedLhsSubOrDimId = lhsVariable.subscripts[mappedLhsDimIndex]
293
+ if (isIndex(mappedLhsSubOrDimId)) {
294
+ // This is a specific subscript (i.e., an index); dereference the array using the index
295
+ // number of the _mapped_ subscript
296
+ const mappedLhsSubId = mappedLhsSubOrDimId
297
+ const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
298
+ const lhsDim = sub(mappedLhsDimId)
299
+ const rhsDim = sub(rhsSubId)
300
+ const lhsSubIndex = lhsDim.value.indexOf(mappedLhsSubId)
301
+ if (lhsSubIndex >= 0) {
302
+ const mappedSubId = rhsDim.mappings[mappedLhsDimId][lhsSubIndex]
303
+ return `${sub(mappedSubId).value}`
304
+ } else {
305
+ throw new Error(
306
+ `Failed to find mapped LHS subscript ${mappedLhsSubId} for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`
307
+ )
269
308
  }
309
+ } else {
310
+ // Determine the dimension mapping
311
+ const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
312
+ const indexVarName = loopIndexVars.index(mappedLhsDimId)
313
+ return `__map${rhsSubId}${mappedLhsDimId}[${indexVarName}]`
270
314
  }
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
315
  } else {
281
- return `${rhsSubId}[${indexName}]`
316
+ throw new Error(`Failed to find LHS dimension for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`)
282
317
  }
283
318
  }