@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 +3 -4
- package/src/_shared/helpers.js +8 -39
- package/src/_shared/subscript.js +1 -23
- package/src/generate/gen-const-list.js +9 -19
- package/src/generate/gen-equation.js +109 -73
- package/src/generate/gen-expr.js +67 -24
- package/src/generate/loop-index-vars.js +12 -0
- package/src/index.js +5 -1
- package/src/model/model.js +2 -6
- package/src/model/read-equation-fn-delay.js +55 -29
- package/src/model/read-equation-fn-game.js +2 -2
- package/src/model/read-equation-fn-npv.js +10 -3
- package/src/model/read-equation-fn-smooth.js +15 -8
- package/src/model/read-equation-fn-trend.js +6 -2
- package/src/model/read-equation-fn-with-lookup.js +1 -1
- package/src/model/read-equations.js +273 -119
- package/src/model/read-variables.js +13 -21
- package/src/parse-and-generate.js +10 -15
- package/src/preprocess/preprocessor.js +0 -247
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/compile",
|
|
3
|
-
"version": "0.7.
|
|
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.
|
|
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
|
}
|
package/src/_shared/helpers.js
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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)
|
package/src/_shared/subscript.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
16
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
74
|
-
// [_a2
|
|
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(
|
|
40
|
-
const arrayIndexVars = new LoopIndexVars(
|
|
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
|
|
76
|
-
const indexName = loopIndexVars.index(
|
|
77
|
-
const dimLength = sub(
|
|
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,
|
|
146
|
+
cVarRef: varRef => cVarRef(variable, varRef, loopIndexVars, arrayIndexVars),
|
|
147
147
|
cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
|
|
148
|
-
cVarIndex: subOrDimId => cVarIndex(variable,
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
317
|
+
throw new Error(`Failed to find LHS dimension for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`)
|
|
282
318
|
}
|
|
283
319
|
}
|
package/src/generate/gen-expr.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cdbl, newTmpVarName } from '../_shared/helpers.js'
|
|
2
|
-
import { extractMarkedDims, isDimension, isIndex,
|
|
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 =
|
|
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
|
-
//
|
|
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
|
|
844
|
-
const reqSubIds = reqArg.subscriptRefs.map(subRef => subRef.subId)
|
|
854
|
+
const reqRef = cVarRefWithoutLastIndices(reqArg, 1)
|
|
845
855
|
|
|
846
|
-
// Process the priority argument
|
|
847
|
-
|
|
848
|
-
|
|
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
|
|
864
|
+
const availRef = ctx.cVarRef(availArg)
|
|
853
865
|
|
|
854
|
-
// The `ALLOCATE AVAILABLE` function iterates over the subscript in its first arg
|
|
855
|
-
|
|
856
|
-
|
|
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
|
|
874
|
+
const numRequesters = sub(allocDimId).size
|
|
861
875
|
switch (ctx.outFormat) {
|
|
862
876
|
case 'c':
|
|
863
877
|
ctx.emitPreInnerLoop(
|
|
864
|
-
` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${
|
|
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(${
|
|
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}[${
|
|
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
|
+
}
|