@sdeverywhere/compile 0.7.21 → 0.7.23
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 +1 -1
- package/src/generate/gen-code-c.js +10 -15
- package/src/generate/gen-code-js.js +8 -3
- package/src/generate/gen-equation.js +5 -6
- package/src/generate/gen-lookup-from-points.js +17 -4
- package/src/model/model.js +5 -1
- package/src/model/read-equations.js +3 -0
- package/src/model/read-variables.js +28 -5
- package/src/model/toposort.js +1 -1
package/package.json
CHANGED
|
@@ -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
|
|
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 += `
|
|
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
|
|
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}
|
|
551
|
+
c += ` lookup = ${lookupVar};\n`
|
|
547
552
|
c += ` break;`
|
|
548
553
|
return c
|
|
549
554
|
})
|
|
@@ -113,13 +113,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
113
113
|
// Apply special handling for lookup variables. The data for lookup variables is already
|
|
114
114
|
// defined as a set of explicit data points (stored in the `Variable` instance).
|
|
115
115
|
if (variable.isLookup()) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
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]
|
|
120
120
|
} else {
|
|
121
|
-
|
|
122
|
-
return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
|
|
121
|
+
return []
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -16,10 +16,23 @@ import { isDimension, isTrivialDimension, sub } from '../_shared/subscript.js'
|
|
|
16
16
|
*/
|
|
17
17
|
export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndexVars, outFormat) {
|
|
18
18
|
if (variable.points.length === 0) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// In the case where the lookup data array is empty (typically this only occurs in the
|
|
20
|
+
// case of game inputs), generate a new empty lookup
|
|
21
|
+
if (mode === 'decl') {
|
|
22
|
+
// Nothing to emit in decl mode
|
|
23
|
+
return []
|
|
24
|
+
} else if (mode === 'init-lookups') {
|
|
25
|
+
// In init mode, generate a new empty lookup
|
|
26
|
+
switch (outFormat) {
|
|
27
|
+
case 'c':
|
|
28
|
+
return [` ${varLhs} = __new_lookup(0, /*copy=*/false, NULL);`]
|
|
29
|
+
case 'js':
|
|
30
|
+
return [` ${varLhs} = fns.createLookup(0, undefined);`]
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`Unhandled output format '${outFormat}'`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else if (copy) {
|
|
23
36
|
// Inline the data points and copy them when initializing the lookup
|
|
24
37
|
if (mode === 'decl') {
|
|
25
38
|
// Nothing to emit in decl mode
|
package/src/model/model.js
CHANGED
|
@@ -71,6 +71,10 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) {
|
|
|
71
71
|
// Some arrays need to be separated into variables with individual indices to
|
|
72
72
|
// prevent eval cycles. They are manually added to the spec file.
|
|
73
73
|
let specialSeparationDims = spec.specialSeparationDims
|
|
74
|
+
// All arrays that have one of the specified dimension ID lists are
|
|
75
|
+
// separated on those dimensions. This allows variables to be separated
|
|
76
|
+
// without listing each one.
|
|
77
|
+
let separateAllVarsWithDims = spec.separateAllVarsWithDims
|
|
74
78
|
|
|
75
79
|
// Dimensions must be defined before reading variables that use them.
|
|
76
80
|
readDimensionDefs(parsedModel, modelDirname)
|
|
@@ -79,7 +83,7 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) {
|
|
|
79
83
|
if (opts?.stopAfterResolveSubscripts) return
|
|
80
84
|
|
|
81
85
|
// Read variables from the model parse tree.
|
|
82
|
-
const vars = readVariables(parsedModel, specialSeparationDims)
|
|
86
|
+
const vars = readVariables(parsedModel, specialSeparationDims, separateAllVarsWithDims)
|
|
83
87
|
|
|
84
88
|
// Include a placeholder variable for the exogenous `Time` variable
|
|
85
89
|
const timeVar = new Variable()
|
|
@@ -114,6 +114,9 @@ class Context {
|
|
|
114
114
|
const parsedModel = { kind: 'vensim', root: parseVensimModel(eqnText) }
|
|
115
115
|
|
|
116
116
|
// Create one or more `Variable` instances from the equations
|
|
117
|
+
// TODO: Technically we should pass `spec.separateAllVarsWithDims` here so that any
|
|
118
|
+
// generated apply-to-all variables will be separated according to the spec like other
|
|
119
|
+
// normal variables
|
|
117
120
|
const vars = readVariables(parsedModel)
|
|
118
121
|
|
|
119
122
|
// Add the variables to the `Model`
|
|
@@ -12,17 +12,21 @@ import Variable from './variable.js'
|
|
|
12
12
|
*
|
|
13
13
|
* @param {*} parsedModel TODO: Use ParsedVensimModel type here
|
|
14
14
|
* @param {Object.<string, string>} [specialSeparationDims] The variable names that need to be
|
|
15
|
-
* separated because of circular references.
|
|
16
|
-
* name to separate on.
|
|
15
|
+
* separated because of circular references. This is an optional mapping from "C" variable name
|
|
16
|
+
* to "C" dimension name to separate on.
|
|
17
|
+
* @param {string[][]} [separateAllVarsWithDims] An optional array containing arrays of dimension
|
|
18
|
+
* identifiers for which variables will be separated. For example, if the LHS is `x[DimA,DimB,DimC]`
|
|
19
|
+
* and this array contains a list `['_dima', '_dimb']`, then the LHS will be separated on both
|
|
20
|
+
* DimA and DimB.
|
|
17
21
|
* @returns {*} An array containing all `Variable` instances that were generated from
|
|
18
22
|
* the model equations.
|
|
19
23
|
*/
|
|
20
|
-
export function readVariables(parsedModel, specialSeparationDims) {
|
|
24
|
+
export function readVariables(parsedModel, specialSeparationDims, separateAllVarsWithDims) {
|
|
21
25
|
const variables = []
|
|
22
26
|
|
|
23
27
|
// Add one or more `Variable` definitions for each parsed equation
|
|
24
28
|
for (const eqn of parsedModel.root.equations) {
|
|
25
|
-
variables.push(...variablesForEquation(eqn, specialSeparationDims || {}))
|
|
29
|
+
variables.push(...variablesForEquation(eqn, specialSeparationDims || {}, separateAllVarsWithDims || []))
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
return variables
|
|
@@ -37,10 +41,12 @@ export function readVariables(parsedModel, specialSeparationDims) {
|
|
|
37
41
|
* @param {*} eqn The parsed equation.
|
|
38
42
|
* @param {Object.<string, string>} specialSeparationDims The variable names that need to be
|
|
39
43
|
* separated because of circular references.
|
|
44
|
+
* @param {string[][]} separateAllVarsWithDims An array containing arrays of dimension
|
|
45
|
+
* identifiers for which variables will be separated.
|
|
40
46
|
* @returns {*} An array containing all `Variable` instances that were generated from
|
|
41
47
|
* the given equation.
|
|
42
48
|
*/
|
|
43
|
-
function variablesForEquation(eqn, specialSeparationDims) {
|
|
49
|
+
function variablesForEquation(eqn, specialSeparationDims, separateAllVarsWithDims) {
|
|
44
50
|
// Start a new variable defined by this equation
|
|
45
51
|
const variable = new Variable()
|
|
46
52
|
|
|
@@ -115,6 +121,23 @@ function variablesForEquation(eqn, specialSeparationDims) {
|
|
|
115
121
|
if (!Array.isArray(separationDims)) {
|
|
116
122
|
separationDims = [separationDims]
|
|
117
123
|
}
|
|
124
|
+
// Alternatively, if the variable was not in `specialSeparationDims`, separate
|
|
125
|
+
// on dims from `separateAllVarsWithDims` if the var matches one of the dim lists.
|
|
126
|
+
if (separationDims.length === 0) {
|
|
127
|
+
for (let dimList of separateAllVarsWithDims) {
|
|
128
|
+
// Technically we allow each entry in the spec array to be either a single
|
|
129
|
+
// dim ID string or an array of dim IDs, so convert to array if needed
|
|
130
|
+
if (!Array.isArray(dimList)) {
|
|
131
|
+
dimList = [dimList]
|
|
132
|
+
}
|
|
133
|
+
// The list entry from the spec is only considered a match if every dimension
|
|
134
|
+
// in the spec list appears on the LHS
|
|
135
|
+
if (dimList.every(dim => subIds.includes(dim))) {
|
|
136
|
+
separationDims = dimList
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
118
141
|
positionsToExpand = subscriptPositionsToExpand(subIds, exceptSubIdSets, separationDims, variable.modelFormula)
|
|
119
142
|
}
|
|
120
143
|
|
package/src/model/toposort.js
CHANGED
|
@@ -42,7 +42,7 @@ function toposort(nodes, edges) {
|
|
|
42
42
|
} catch (e) {
|
|
43
43
|
nodeRep = ''
|
|
44
44
|
}
|
|
45
|
-
throw new Error('Found cyclic dependency during toposort:\n' + [...predecessors].join(' →\n') + nodeRep)
|
|
45
|
+
throw new Error('Found cyclic dependency during toposort:\n' + [...predecessors].join(' →\n') + ' →' + nodeRep)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (!nodesHash.has(node)) {
|