@sdeverywhere/compile 0.7.9 → 0.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -6
- package/src/_shared/helpers.js +11 -11
- package/src/_shared/subscript.js +5 -0
- package/src/generate/code-gen.js +38 -42
- package/src/generate/direct-data-helpers.js +86 -0
- package/src/generate/equation-gen.js +79 -52
- package/src/generate/expand-var-names.js +115 -0
- package/src/generate/gen-const-list.js +89 -0
- package/src/generate/gen-direct-const.js +91 -0
- package/src/generate/gen-equation.js +273 -0
- package/src/generate/gen-expr.js +851 -0
- package/src/generate/gen-lookup-from-direct.js +105 -0
- package/src/generate/gen-lookup-from-external.js +115 -0
- package/src/generate/gen-lookup-from-points.js +85 -0
- package/src/model/equation-reader.js +32 -26
- package/src/model/model.js +160 -17
- package/src/model/read-equation-fn-delay.js +312 -0
- package/src/model/read-equation-fn-npv.js +67 -0
- package/src/model/read-equation-fn-smooth.js +133 -0
- package/src/model/read-equation-fn-trend.js +44 -0
- package/src/model/read-equation-fn-with-lookup.js +37 -0
- package/src/model/read-equations.js +795 -0
- package/src/model/read-subscripts.js +77 -0
- package/src/model/read-variables.js +264 -0
- package/src/model/reduce-variables.js +166 -0
- package/src/model/subscript-range-reader.js +4 -1
- package/src/model/variable.js +7 -2
- package/src/parse/parser.js +8 -4
- package/src/parse-and-generate.js +65 -12
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { cartesianProductOf, cdbl } from '../_shared/helpers.js'
|
|
2
|
+
import { isDimension, normalizeSubscripts, sub } from '../_shared/subscript.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate code for a single element in a const list definition.
|
|
6
|
+
*
|
|
7
|
+
* @param {*} variable The `Variable` instance to process.
|
|
8
|
+
* @param {*} parsedEqn The parsed equation.
|
|
9
|
+
* @return {string[]} An array of strings containing the generated C code for the variable,
|
|
10
|
+
* one string per line of code.
|
|
11
|
+
*/
|
|
12
|
+
export function generateConstListElement(variable, parsedEqn) {
|
|
13
|
+
// In the "read variables" phase, const lists are expanded into separated variable
|
|
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.
|
|
18
|
+
//
|
|
19
|
+
// In the following example,
|
|
20
|
+
// we have a 2D variable whose original dimensions are not in normal order:
|
|
21
|
+
// DimA: A1, A2 ~~|
|
|
22
|
+
// DimB: B1, B2, B3 ~~|
|
|
23
|
+
// x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~|
|
|
24
|
+
//
|
|
25
|
+
// The variable `x` will have been separated into:
|
|
26
|
+
// x[B1,A1]
|
|
27
|
+
// x[B1,A2]
|
|
28
|
+
// x[B2,A1]
|
|
29
|
+
// ...
|
|
30
|
+
//
|
|
31
|
+
// Each one will refer to a single element from the original const list. To determine
|
|
32
|
+
// which element in the const list goes with which variable instance, we build an array
|
|
33
|
+
// of all subscript combinations and then find the index of the one that matches the
|
|
34
|
+
// combination used for the separated variable instance.
|
|
35
|
+
const lhsSubRefs = variable.parsedEqn.lhs.varDef.subscriptRefs
|
|
36
|
+
const lhsSubIds = lhsSubRefs.map(subRef => subRef.subId)
|
|
37
|
+
const subIdArrays = lhsSubIds.map(subOrDimId => {
|
|
38
|
+
if (isDimension(subOrDimId)) {
|
|
39
|
+
// Use the full array of subscripts (indexes) for the dimension at this position
|
|
40
|
+
return sub(subOrDimId).value
|
|
41
|
+
} else {
|
|
42
|
+
// This is a single subscript (index), so use an array with a single element
|
|
43
|
+
return [subOrDimId]
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Continuing with the above example, at this point we will have a 2D array:
|
|
48
|
+
// [
|
|
49
|
+
// [_b1,_b2,_b3],
|
|
50
|
+
// [_a1,_a2]
|
|
51
|
+
// ]
|
|
52
|
+
// We expand these into the set of all combinations of subscripts in the original
|
|
53
|
+
// order of the dimensions from the equation LHS.
|
|
54
|
+
const origCombos = cartesianProductOf(subIdArrays)
|
|
55
|
+
|
|
56
|
+
// Now we have the combinations in original order:
|
|
57
|
+
// [_b1,_a1]
|
|
58
|
+
// [_b1,_a2]
|
|
59
|
+
// [_b2,_a1]
|
|
60
|
+
// ...
|
|
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(''))
|
|
71
|
+
|
|
72
|
+
// 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]
|
|
75
|
+
const lhsComboString = variable.subscripts.map(subId => `[${subId}]`).join('')
|
|
76
|
+
|
|
77
|
+
// Find the index of the combination that matches `variable.subscripts`
|
|
78
|
+
const constIndex = comboStrings.indexOf(lhsComboString)
|
|
79
|
+
if (constIndex < 0) {
|
|
80
|
+
throw new Error(`Failed to determine index of const list element for ${variable.refId}`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Determine the LHS and RHS of the const assignment
|
|
84
|
+
const lhsVarId = variable.varName
|
|
85
|
+
const lhsIndicesString = variable.subscripts.map(subId => `[${sub(subId).value}]`).join('')
|
|
86
|
+
const lhsRef = `${lhsVarId}${lhsIndicesString}`
|
|
87
|
+
const rhsConstValue = cdbl(parsedEqn.rhs.constants[constIndex].value)
|
|
88
|
+
return ` ${lhsRef} = ${rhsConstValue};`
|
|
89
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import XLSX from 'xlsx'
|
|
2
|
+
|
|
3
|
+
import { cartesianProductOf } from '../_shared/helpers.js'
|
|
4
|
+
import { indexInSepDim, isDimension, sub } from '../_shared/subscript.js'
|
|
5
|
+
|
|
6
|
+
import { handleExcelOrCsvFile } from './direct-data-helpers.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate code for a variable that uses `GET DIRECT CONSTANTS` to source constant values from an external
|
|
10
|
+
* file (in CSV or Excel format).
|
|
11
|
+
*
|
|
12
|
+
* @param {*} variable The `Variable` instance to process.
|
|
13
|
+
* @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT CONSTANTS` call (e.g.,
|
|
14
|
+
* `?data`) to the tabular data contained in the loaded data file.
|
|
15
|
+
* @param {string} modelDir The path to the directory containing the model (used for resolving data files).
|
|
16
|
+
* @return {string[]} An array of strings containing the generated C code for the variable,
|
|
17
|
+
* one string per line of code.
|
|
18
|
+
*/
|
|
19
|
+
export function generateDirectConstInit(variable, directData, modelDir) {
|
|
20
|
+
// Create a function that reads the CSV or XLS[X] content
|
|
21
|
+
let { file, tab, startCell } = variable.directConstArgs
|
|
22
|
+
let getCellValue = handleExcelOrCsvFile(file, tab, 'constants', directData, modelDir)
|
|
23
|
+
|
|
24
|
+
// Get C subscripts in text form for the LHS in normal order.
|
|
25
|
+
let lhsSubIds = variable.parsedEqn.lhs.varDef.subscriptRefs?.map(s => s.subId) || []
|
|
26
|
+
let modelDimNames = lhsSubIds.filter(s => isDimension(s))
|
|
27
|
+
|
|
28
|
+
// Generate offsets from the start cell in the table corresponding to LHS indices.
|
|
29
|
+
let cellOffsets = []
|
|
30
|
+
let cSubscripts = variable.subscripts.map(s => (isDimension(s) ? sub(s).value : [s]))
|
|
31
|
+
let lhsIndexSubscripts = cartesianProductOf(cSubscripts)
|
|
32
|
+
|
|
33
|
+
// Find the table cell offset for each LHS index tuple.
|
|
34
|
+
let lines = []
|
|
35
|
+
for (let indexSubscripts of lhsIndexSubscripts) {
|
|
36
|
+
let entry = [null, null]
|
|
37
|
+
for (let i = 0; i < variable.subscripts.length; i++) {
|
|
38
|
+
// LHS dimensions or indices in a separated dimension map to table cells.
|
|
39
|
+
let lhsSubscript = variable.subscripts[i]
|
|
40
|
+
if (isDimension(lhsSubscript) || indexInSepDim(lhsSubscript, variable)) {
|
|
41
|
+
// Consider the LHS index subscript at this position.
|
|
42
|
+
let indexSubscript = indexSubscripts[i]
|
|
43
|
+
let ind = sub(indexSubscript)
|
|
44
|
+
// Find the model subscript position corresponding to the LHS index subscript.
|
|
45
|
+
for (let iModelDim = 0; iModelDim < modelDimNames.length; iModelDim++) {
|
|
46
|
+
// Only fill an entry position once.
|
|
47
|
+
if (entry[iModelDim] === null) {
|
|
48
|
+
let modelDim = sub(modelDimNames[iModelDim])
|
|
49
|
+
if (modelDim.family === ind.family) {
|
|
50
|
+
// Set the numeric index for the model dimension in the cell offset entry.
|
|
51
|
+
// Use the position within the dimension to map subdimensions onto cell offsets.
|
|
52
|
+
let pos = modelDim.value.indexOf(indexSubscript)
|
|
53
|
+
// Vectors use a 2D cell offset that maps to columns in the first row.
|
|
54
|
+
// Tables use a 2D cell offset with the row or column matching the model dimension.
|
|
55
|
+
let entryRowOrCol = modelDimNames.length > 1 ? iModelDim : 1
|
|
56
|
+
entry[entryRowOrCol] = pos
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Replace unfilled entry positions with zero.
|
|
64
|
+
entry = entry.map(x => (x === null ? 0 : x))
|
|
65
|
+
// Read values by column first when the start cell ends with an asterisk.
|
|
66
|
+
// Ref: https://www.vensim.com/documentation/fn_get_direct_constants.html
|
|
67
|
+
if (startCell.endsWith('*')) {
|
|
68
|
+
entry.reverse()
|
|
69
|
+
}
|
|
70
|
+
cellOffsets.push(entry)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Read tabular data into an indexed variable for each cell.
|
|
74
|
+
let numericSubscripts = lhsIndexSubscripts.map(idx => idx.map(s => sub(s).value))
|
|
75
|
+
let lhsSubscripts = numericSubscripts.map(s => s.reduce((a, v) => a.concat(`[${v}]`), ''))
|
|
76
|
+
let dataAddress = XLSX.utils.decode_cell(startCell.toUpperCase())
|
|
77
|
+
let startCol = dataAddress.c
|
|
78
|
+
let startRow = dataAddress.r
|
|
79
|
+
if (startCol < 0 || startRow < 0) {
|
|
80
|
+
throw new Error(`Failed to parse 'cell' argument for GET DIRECT CONSTANTS call for ${variable.refId}: ${startCell}`)
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i < cellOffsets.length; i++) {
|
|
83
|
+
let rowOffset = cellOffsets[i][0] ? cellOffsets[i][0] : 0
|
|
84
|
+
let colOffset = cellOffsets[i][1] ? cellOffsets[i][1] : 0
|
|
85
|
+
let dataValue = getCellValue(startCol + colOffset, startRow + rowOffset)
|
|
86
|
+
let lhs = `${variable.varName}${lhsSubscripts[i] || ''}`
|
|
87
|
+
lines.push(` ${lhs} = ${dataValue};`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return lines
|
|
91
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dimensionNames,
|
|
3
|
+
hasMapping,
|
|
4
|
+
isDimension,
|
|
5
|
+
isIndex,
|
|
6
|
+
isTrivialDimension,
|
|
7
|
+
normalizeSubscripts,
|
|
8
|
+
separatedVariableIndex,
|
|
9
|
+
sub
|
|
10
|
+
} from '../_shared/subscript.js'
|
|
11
|
+
import { generateConstListElement } from './gen-const-list.js'
|
|
12
|
+
|
|
13
|
+
import { generateDirectConstInit } from './gen-direct-const.js'
|
|
14
|
+
import { generateExpr } from './gen-expr.js'
|
|
15
|
+
import { generateLookupsFromDirectData } from './gen-lookup-from-direct.js'
|
|
16
|
+
import { generateLookupsFromExternalData } from './gen-lookup-from-external.js'
|
|
17
|
+
import { generateLookupFromPoints } from './gen-lookup-from-points.js'
|
|
18
|
+
|
|
19
|
+
import LoopIndexVars from './loop-index-vars.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate C code for the given model equation.
|
|
23
|
+
*
|
|
24
|
+
* TODO: Variable type
|
|
25
|
+
* TODO: Define type for mode values
|
|
26
|
+
*
|
|
27
|
+
* @param {*} variable The `Variable` instance to process.
|
|
28
|
+
* @param {'decl' | 'init-constants' | 'init-lookups' | 'init-levels' | 'eval'} mode The code generation mode.
|
|
29
|
+
* @param {Map<string, any>} extData The map of datasets from external `.dat` files.
|
|
30
|
+
* @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA` call (e.g.,
|
|
31
|
+
* `?data`) to the tabular data contained in the loaded data file.
|
|
32
|
+
* @param {string} modelDir The path to the directory containing the model (used for resolving data files).
|
|
33
|
+
* @return {string[]} An array of strings containing the generated C code for the variable,
|
|
34
|
+
* one string per line of code.
|
|
35
|
+
*/
|
|
36
|
+
export function generateEquation(variable, mode, extData, directData, modelDir) {
|
|
37
|
+
// Maps of LHS subscript families to loop index vars for lookup on the RHS
|
|
38
|
+
const loopIndexVars = new LoopIndexVars(['i', 'j', 'k', 'l', 'm'])
|
|
39
|
+
const arrayIndexVars = new LoopIndexVars(['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r'])
|
|
40
|
+
|
|
41
|
+
// Generate the LHS variable reference code
|
|
42
|
+
const parsedEqn = variable.parsedEqn
|
|
43
|
+
const cLhs = cVarRefWithLhsSubscripts(variable, parsedEqn.lhs.varDef.varId, loopIndexVars)
|
|
44
|
+
|
|
45
|
+
// Include the original model equation in a comment that comes before the generated code
|
|
46
|
+
// for that equation
|
|
47
|
+
const modelFormula = variable.origModelFormula || variable.modelFormula
|
|
48
|
+
const comment = ` // ${variable.modelLHS} = ${modelFormula.replace(/\n/g, '')}`
|
|
49
|
+
|
|
50
|
+
// Apply special handling for const lists
|
|
51
|
+
if (parsedEqn.rhs.kind === 'const-list') {
|
|
52
|
+
if (mode !== 'init-constants' && mode !== 'eval') {
|
|
53
|
+
throw new Error(`Invalid code gen mode '${mode}' for const list variable ${variable.modelLHS}`)
|
|
54
|
+
}
|
|
55
|
+
// XXX: The legacy code gen emitted a comment before each init statement, so we will
|
|
56
|
+
// do the same for now to maintain compatibility
|
|
57
|
+
return [comment, generateConstListElement(variable, parsedEqn)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Emit direct constants individually without separating them first
|
|
61
|
+
if (variable.directConstArgs) {
|
|
62
|
+
const initCode = generateDirectConstInit(variable, directData, modelDir)
|
|
63
|
+
return [comment, ...initCode]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get the dimension IDs for the LHS variable
|
|
67
|
+
const dimIds = dimensionNames(variable.subscripts)
|
|
68
|
+
|
|
69
|
+
// Turn each dimension ID into a loop with a loop index variable.
|
|
70
|
+
// If the variable has no subscripts, nothing will be emitted here.
|
|
71
|
+
const openLoops = []
|
|
72
|
+
const closeLoops = []
|
|
73
|
+
for (const dimId of dimIds) {
|
|
74
|
+
const indexName = loopIndexVars.index(dimId)
|
|
75
|
+
const dimLength = sub(dimId).size
|
|
76
|
+
openLoops.push(` for (size_t ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
|
|
77
|
+
closeLoops.push(' }')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Apply special handling for data variables. The data can be defined in one of three ways:
|
|
81
|
+
// - as a set of explicit data points (stored in the `Variable` instance), or
|
|
82
|
+
// - from an external file via a `GET DIRECT DATA` call, or
|
|
83
|
+
// - from an external data file (i.e., a "normal" data variable)
|
|
84
|
+
if (variable.isData()) {
|
|
85
|
+
if (variable.points.length > 0) {
|
|
86
|
+
// The variable already has data points defined, so generate a new lookup using that data.
|
|
87
|
+
// Note that unlike the other lookup cases, this one needs to include loop open/close code
|
|
88
|
+
// if the variable is subscripted.
|
|
89
|
+
const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ true, cLhs, loopIndexVars)
|
|
90
|
+
if (lookupDef.length > 0) {
|
|
91
|
+
return [comment, ...openLoops, ...lookupDef, ...closeLoops]
|
|
92
|
+
} else {
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
} else if (variable.directDataArgs) {
|
|
96
|
+
// The data is referenced using a `GET DIRECT DATA` call; generate one or more lookups
|
|
97
|
+
// using the data defined in external files
|
|
98
|
+
return generateLookupsFromDirectData(variable, mode, directData, modelDir, cLhs)
|
|
99
|
+
} else {
|
|
100
|
+
// This is a "normal" data variable; generate one or more lookups using the data defined
|
|
101
|
+
// in external files
|
|
102
|
+
return generateLookupsFromExternalData(variable, mode, extData, cLhs)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Apply special handling for lookup variables. The data for lookup variables is already
|
|
107
|
+
// defined as a set of explicit data points (stored in the `Variable` instance).
|
|
108
|
+
if (variable.isLookup()) {
|
|
109
|
+
return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Keep a buffer of code that will be included before the innermost loop
|
|
113
|
+
const preInnerLoopLines = []
|
|
114
|
+
|
|
115
|
+
// Keep a buffer of code that will be included before the generated primary formula
|
|
116
|
+
const preFormulaLines = []
|
|
117
|
+
|
|
118
|
+
// Keep a buffer of code that will be included after the generated primary formula
|
|
119
|
+
const postFormulaLines = []
|
|
120
|
+
|
|
121
|
+
// Keep track of marked dimensions
|
|
122
|
+
const markedDimIds = new Set()
|
|
123
|
+
|
|
124
|
+
// Generate code for an equation with an expression on the RHS
|
|
125
|
+
const genExprCtx = {
|
|
126
|
+
variable,
|
|
127
|
+
mode,
|
|
128
|
+
cLhs,
|
|
129
|
+
loopIndexVars,
|
|
130
|
+
arrayIndexVars,
|
|
131
|
+
resetMarkedDims: () => markedDimIds.clear(),
|
|
132
|
+
addMarkedDim: dimId => markedDimIds.add(dimId),
|
|
133
|
+
emitPreInnerLoop: s => preInnerLoopLines.push(s),
|
|
134
|
+
emitPreFormula: s => preFormulaLines.push(s),
|
|
135
|
+
emitPostFormula: s => postFormulaLines.push(s),
|
|
136
|
+
cVarRef: varRef => cVarRef(variable, varRef, markedDimIds, loopIndexVars, arrayIndexVars),
|
|
137
|
+
cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
|
|
138
|
+
cVarIndex: subOrDimId => cVarIndex(variable, [subOrDimId], subOrDimId, markedDimIds, loopIndexVars, arrayIndexVars)
|
|
139
|
+
}
|
|
140
|
+
const cRhs = generateExpr(parsedEqn.rhs.expr, genExprCtx)
|
|
141
|
+
const formula = ` ${cLhs} = ${cRhs};`
|
|
142
|
+
|
|
143
|
+
// Insert the pre-inner loop code, if needed
|
|
144
|
+
if (preInnerLoopLines.length > 0) {
|
|
145
|
+
openLoops.splice(variable.subscripts.length - 1, 0, ...preInnerLoopLines)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Combine all lines of comments and code into a single array
|
|
149
|
+
return [comment, ...openLoops, ...preFormulaLines, formula, ...postFormulaLines, ...closeLoops]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Return the C code for a subscripted reference to the given variable using the LHS subscripts
|
|
154
|
+
* of the given equation.
|
|
155
|
+
*
|
|
156
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
157
|
+
* @param {*} baseVarId The base variable ID to which the subscript parts will be appended.
|
|
158
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
159
|
+
* @return {string} The C variable reference.
|
|
160
|
+
*/
|
|
161
|
+
function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
|
|
162
|
+
const lhsSubIds = lhsVariable.subscripts
|
|
163
|
+
const cSubParts = lhsSubIds.map(subId => {
|
|
164
|
+
if (isDimension(subId)) {
|
|
165
|
+
const i = loopIndexVars.index(subId)
|
|
166
|
+
if (isTrivialDimension(subId)) {
|
|
167
|
+
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
168
|
+
return `[${i}]`
|
|
169
|
+
} else {
|
|
170
|
+
return `[${subId}][${i}]`
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
return `[${sub(subId).value}]`
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
return `${baseVarId}${cSubParts.join('')}`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Return the C code for a RHS (possibly subscripted) variable reference.
|
|
181
|
+
*
|
|
182
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
183
|
+
* @param {*} rhsVarRef The `VariableRef` used in a RHS expression.
|
|
184
|
+
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
185
|
+
* in an array function, for example `SUM(x[DimA!])`.
|
|
186
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
187
|
+
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
188
|
+
* (that use marked dimensions).
|
|
189
|
+
* @returns {string} The C variable reference.
|
|
190
|
+
*/
|
|
191
|
+
function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndexVars) {
|
|
192
|
+
if (rhsVarRef.subscriptRefs === undefined) {
|
|
193
|
+
// No subscripts, so return the base variable ID
|
|
194
|
+
return rhsVarRef.varId
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Normalize the RHS subscripts
|
|
198
|
+
let rhsSubIds
|
|
199
|
+
try {
|
|
200
|
+
// XXX: For now, strip the mark here (need to revisit this)
|
|
201
|
+
rhsSubIds = normalizeSubscripts(rhsVarRef.subscriptRefs.map(subRef => subRef.subId.replace('!', '')))
|
|
202
|
+
} catch (e) {
|
|
203
|
+
throw new Error(`normalizeSubscripts failed in rhsVarRef: refId=${lhsVariable.refId} error=${e}`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Determine the subscript code (array lookup) for each dimension. For example, if
|
|
207
|
+
// the RHS variable reference in the model looks like `x[DimA]`, this will convert the
|
|
208
|
+
// `[DimA]` part to `[_dima[i]]` (or simply `[i]` if it is a "trivial" dimension).
|
|
209
|
+
const cSubParts = rhsSubIds.map(rhsSubId => {
|
|
210
|
+
return cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return `${rhsVarRef.varId}${cSubParts.map(part => `[${part}]`).join('')}`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Return the C code for indexing into a subscripted variable.
|
|
218
|
+
*
|
|
219
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
220
|
+
* @param {string[]} rhsSubIds The set of all subscript or dimension IDs used on the RHS.
|
|
221
|
+
* @param {string} rhsSubId The specific subscript or dimension ID being evaluated.
|
|
222
|
+
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
223
|
+
* in an array function, for example `SUM(x[DimA!])`.
|
|
224
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
225
|
+
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
226
|
+
* (that use marked dimensions).
|
|
227
|
+
* @returns {string} The C variable reference.
|
|
228
|
+
*/
|
|
229
|
+
function cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars) {
|
|
230
|
+
if (isIndex(rhsSubId)) {
|
|
231
|
+
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
232
|
+
// number of the subscript
|
|
233
|
+
return `${sub(rhsSubId).value}`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Otherwise, this is a dimension. Get the corresponding loop index variable used
|
|
237
|
+
// in the "for" loop.
|
|
238
|
+
let indexName
|
|
239
|
+
if (markedDimIds.has(rhsSubId)) {
|
|
240
|
+
// This is a marked dimension as used in an array function (e.g., `SUM`), so use
|
|
241
|
+
// the name of the array loop index variable
|
|
242
|
+
indexName = arrayIndexVars.index(rhsSubId)
|
|
243
|
+
} else {
|
|
244
|
+
// Use the single index name for a separated variable if it exists
|
|
245
|
+
const separatedIndexName = separatedVariableIndex(rhsSubId, lhsVariable, rhsSubIds)
|
|
246
|
+
if (separatedIndexName) {
|
|
247
|
+
return `${sub(separatedIndexName).value}`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// See if we need to apply a mapping because the RHS dim is not found on the LHS
|
|
251
|
+
const found = lhsVariable.subscripts.findIndex(lhsSubId => sub(lhsSubId).family === sub(rhsSubId).family)
|
|
252
|
+
if (found < 0) {
|
|
253
|
+
// Find the mapping from the RHS subscript to a LHS subscript
|
|
254
|
+
for (const lhsSubId of lhsVariable.subscripts) {
|
|
255
|
+
if (hasMapping(rhsSubId, lhsSubId)) {
|
|
256
|
+
indexName = loopIndexVars.index(lhsSubId)
|
|
257
|
+
return `__map${rhsSubId}${lhsSubId}[${indexName}]`
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// There is no mapping, so use the loop index for this dim family on the LHS
|
|
263
|
+
indexName = loopIndexVars.index(rhsSubId)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Dereference the array using the corresponding loop index variable
|
|
267
|
+
if (isTrivialDimension(rhsSubId)) {
|
|
268
|
+
// When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
|
|
269
|
+
return `${indexName}`
|
|
270
|
+
} else {
|
|
271
|
+
return `${rhsSubId}[${indexName}]`
|
|
272
|
+
}
|
|
273
|
+
}
|