@sdeverywhere/compile 0.7.10 → 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.
@@ -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
+ }