@sdeverywhere/compile 0.7.24 → 0.7.25
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/_shared/subscript.js +2 -2
- package/src/_shared/var-names.js +31 -0
- package/src/generate/expand-var-names.js +10 -77
- package/src/model/expand-var-instances.js +139 -0
- package/src/model/model.js +98 -37
- package/src/model/toposort.js +1 -1
- package/src/parse-and-generate.js +2 -1
package/package.json
CHANGED
package/src/_shared/subscript.js
CHANGED
|
@@ -107,7 +107,7 @@ export function sub(name) {
|
|
|
107
107
|
let result
|
|
108
108
|
try {
|
|
109
109
|
result = subscripts.get(name)
|
|
110
|
-
} catch (
|
|
110
|
+
} catch (_) {
|
|
111
111
|
console.error(`sub name ${name} not found`)
|
|
112
112
|
}
|
|
113
113
|
return result
|
|
@@ -249,7 +249,7 @@ export function subscriptFamilies(subscripts) {
|
|
|
249
249
|
// Return a list of the subscript families for each subscript.
|
|
250
250
|
try {
|
|
251
251
|
return R.map(subscriptName => sub(subscriptName).family, subscripts)
|
|
252
|
-
} catch (
|
|
252
|
+
} catch (_) {
|
|
253
253
|
console.error(`ERROR: subscript not found in "${subscripts.join(',')}" in subscriptFamilies`)
|
|
254
254
|
}
|
|
255
255
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { canonicalName } from './helpers.js'
|
|
2
|
+
import { isIndex, sub } from './subscript.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert a Vensim variable name to a C name.
|
|
6
|
+
*
|
|
7
|
+
* WARNING: This function requires model analysis to be completed first when the variable
|
|
8
|
+
* has subscripts.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} vensimVarName The full Vensim variable name (can contain subscripts).
|
|
11
|
+
* @returns {string} A canonical C representation of the variable name (e.g., '_variable_name').
|
|
12
|
+
*/
|
|
13
|
+
export function cName(vensimVarName) {
|
|
14
|
+
// Split the variable name from the subscripts
|
|
15
|
+
let matches = vensimVarName.match(/([^[]+)(?:\[([^\]]+)\])?/)
|
|
16
|
+
if (!matches) {
|
|
17
|
+
throw new Error(`Invalid variable name '${vensimVarName}' found when converting to C representation`)
|
|
18
|
+
}
|
|
19
|
+
let cVarName = canonicalName(matches[1])
|
|
20
|
+
if (matches[2]) {
|
|
21
|
+
// The variable name includes subscripts, so split them into individual IDs
|
|
22
|
+
let cSubIds = matches[2].split(',').map(x => canonicalName(x))
|
|
23
|
+
// If a subscript is an index, convert it to an index number to match Vensim data exports
|
|
24
|
+
let cSubIdParts = cSubIds.map(cSubId => {
|
|
25
|
+
return isIndex(cSubId) ? `[${sub(cSubId).value}]` : `[${cSubId}]`
|
|
26
|
+
})
|
|
27
|
+
// Append the subscript parts to the base variable name to create the full reference
|
|
28
|
+
cVarName += cSubIdParts.join('')
|
|
29
|
+
}
|
|
30
|
+
return cVarName
|
|
31
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as R from 'ramda'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { sub, isDimension } from '../_shared/subscript.js'
|
|
3
|
+
import { cName } from '../_shared/var-names.js'
|
|
5
4
|
|
|
5
|
+
import { expandVar } from '../model/expand-var-instances.js'
|
|
6
6
|
import Model from '../model/model.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Return an array of names for all
|
|
9
|
+
* Return an array of names for all accessible variables, sorted alphabetically and expanded to
|
|
10
10
|
* include the full set of subscripted variants for variables that include subscripts.
|
|
11
11
|
*
|
|
12
|
-
* @param
|
|
13
|
-
*
|
|
12
|
+
* @param {*} variables The `Variable` objects to process.
|
|
13
|
+
* @param {boolean} canonical If true, convert names to canonical representation (variable identifiers),
|
|
14
|
+
* otherwise return the original name of each variable as it appears in the model.
|
|
14
15
|
* @returns {string[]} An array of variable names or identifiers.
|
|
15
16
|
*/
|
|
16
17
|
export function expandVarNames(canonical) {
|
|
@@ -19,10 +20,12 @@ export function expandVarNames(canonical) {
|
|
|
19
20
|
R.reduce(
|
|
20
21
|
(a, v) => {
|
|
21
22
|
if (v.varType !== 'lookup' && v.varType !== 'data' && v.includeInOutput) {
|
|
23
|
+
const varInstances = expandVar(v)
|
|
24
|
+
const varNames = varInstances.map(instance => instance.varName)
|
|
22
25
|
if (canonical) {
|
|
23
|
-
return R.concat(a, R.map(
|
|
26
|
+
return R.concat(a, R.map(cName, varNames))
|
|
24
27
|
} else {
|
|
25
|
-
return R.concat(a,
|
|
28
|
+
return R.concat(a, varNames)
|
|
26
29
|
}
|
|
27
30
|
} else {
|
|
28
31
|
return a
|
|
@@ -33,73 +36,3 @@ export function expandVarNames(canonical) {
|
|
|
33
36
|
)
|
|
34
37
|
)
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Return an array of names for the given variable including all subscript variants.
|
|
39
|
-
*
|
|
40
|
-
* @param {*} v A `Variable` instance.
|
|
41
|
-
* @returns {string[]} An array of expanded names for the given variable.
|
|
42
|
-
*/
|
|
43
|
-
function namesForVar(v) {
|
|
44
|
-
if (v.parsedEqn === undefined) {
|
|
45
|
-
// XXX: The special `Time` variable does not have a `parsedEqn`, so use the raw LHS
|
|
46
|
-
return [v.modelLHS]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Expand each variable to get the names of all subscripted variants
|
|
50
|
-
const lhsVarDef = v.parsedEqn.lhs.varDef
|
|
51
|
-
const lhsSubRefs = lhsVarDef.subscriptRefs
|
|
52
|
-
if (lhsSubRefs?.length > 0) {
|
|
53
|
-
// At each position, expand any dimensions or use a subscript (index) directly
|
|
54
|
-
const subOrDimNames = lhsSubRefs.map(subRef => subRef.subName)
|
|
55
|
-
return expandDims(lhsVarDef.varName, subOrDimNames)
|
|
56
|
-
} else {
|
|
57
|
-
// No subscripts, so include a single variable name
|
|
58
|
-
return [lhsVarDef.varName]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Return an array of all expanded subscript combinations.
|
|
64
|
-
*
|
|
65
|
-
* @param {string} baseVarName The base name of the variable.
|
|
66
|
-
* @param {string[]} subOrDimNames The array of subscript or dimension names.
|
|
67
|
-
* @returns {string[]} An array of string representations of subscripted references,
|
|
68
|
-
* e.g., `'x[A1,B1]' ,'x[A1,B2]', ...]`.
|
|
69
|
-
*/
|
|
70
|
-
function expandDims(baseVarName, subOrDimNames) {
|
|
71
|
-
// Expand the dimension for each position
|
|
72
|
-
const expanded = subOrDimNames.map(name => expandDim(name).flat(Infinity))
|
|
73
|
-
|
|
74
|
-
// Expand these into the set of all combinations of subscripts for the variable
|
|
75
|
-
const origCombos = cartesianProductOf(expanded)
|
|
76
|
-
return origCombos.map(combo => {
|
|
77
|
-
const subs = combo.join(',')
|
|
78
|
-
return `${baseVarName}[${subs}]`
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Return an array containing all subscript (index) names in the given dimension. If
|
|
84
|
-
* the given name is a subscript, it will return a single-element array with that
|
|
85
|
-
* subscript name.
|
|
86
|
-
*
|
|
87
|
-
* @param {string} subOrDimName A subscript or dimension name.
|
|
88
|
-
* @returns {string[]} A (possibly nested) array of subscript names.
|
|
89
|
-
*/
|
|
90
|
-
function expandDim(subOrDimName) {
|
|
91
|
-
// Convert the name to an ID
|
|
92
|
-
const subOrDimId = canonicalName(subOrDimName)
|
|
93
|
-
|
|
94
|
-
if (isDimension(subOrDimId)) {
|
|
95
|
-
// Get the object for the dimension
|
|
96
|
-
const dimObj = sub(subOrDimId)
|
|
97
|
-
|
|
98
|
-
// The dimension may contain a mix of individual subscripts (indices) and/or subdimensions,
|
|
99
|
-
// so recursively expand them
|
|
100
|
-
return dimObj.modelValue.map(expandDim)
|
|
101
|
-
} else {
|
|
102
|
-
// This is an individual subscript (index), so return it directly
|
|
103
|
-
return [subOrDimName]
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { cartesianProductOf, canonicalName } from '../_shared/helpers.js'
|
|
2
|
+
import { isDimension, sub } from '../_shared/subscript.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single instance of a variable.
|
|
6
|
+
* @typedef {Object} VarInstance
|
|
7
|
+
* @property {string} varName The full name of the variable instance, e.g., "Variable Name[SubA, SubB]".
|
|
8
|
+
* @property {number[]} [subscriptIndices] The array of subscript indices; only defined if this variable
|
|
9
|
+
* has subscripts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Return an array of names and subscript indices for the given variable, expanded to
|
|
14
|
+
* include all subscript variants.
|
|
15
|
+
*
|
|
16
|
+
* @param {*} v A `Variable` object.
|
|
17
|
+
* @returns {VarInstance[]} An array of `VarInstance` objects corresponding to the expanded variable.
|
|
18
|
+
*/
|
|
19
|
+
export function expandVar(v) {
|
|
20
|
+
if (v.parsedEqn === undefined) {
|
|
21
|
+
// XXX: The special `Time` variable does not have a `parsedEqn`, so use the raw LHS
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
varName: v.modelLHS
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Expand each subscript position to get the names of all subscripted variants
|
|
30
|
+
const lhsVarDef = v.parsedEqn.lhs.varDef
|
|
31
|
+
const lhsSubOrDimIds = v.subscripts
|
|
32
|
+
if (lhsSubOrDimIds === undefined || lhsSubOrDimIds.length === 0) {
|
|
33
|
+
// No subscripts, so include a single variable name
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
varName: lhsVarDef.varName
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// At each position, expand any dimensions or use a subscript (index) directly
|
|
42
|
+
return expandDims(lhsVarDef.varName, lhsSubOrDimIds)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Return an array of all expanded subscript combinations.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} baseVarName The base name of the variable.
|
|
49
|
+
* @param {string[]} subOrDimIds The array of subscript or dimension IDs.
|
|
50
|
+
* @returns {VarInstance[]} An array of `VarInstance` objects with string representations of
|
|
51
|
+
* subscripted references, e.g., `'x[A1,B1]', 'x[A1,B2]', ...`.
|
|
52
|
+
*/
|
|
53
|
+
function expandDims(baseVarName, subOrDimIds) {
|
|
54
|
+
// Expand the dimension for each position
|
|
55
|
+
const expanded = subOrDimIds.map(id => expandSubSpecs(id).flat(Infinity))
|
|
56
|
+
|
|
57
|
+
// Expand these into the set of all combinations of subscripts for the variable
|
|
58
|
+
const origCombos = cartesianProductOf(expanded)
|
|
59
|
+
return origCombos.map(combo => {
|
|
60
|
+
const subNames = combo.map(spec => spec.name).join(',')
|
|
61
|
+
const subIndices = combo.map(spec => spec.index)
|
|
62
|
+
return {
|
|
63
|
+
varName: `${baseVarName}[${subNames}]`,
|
|
64
|
+
subscriptIndices: subIndices
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pairs a subscript name with its index.
|
|
71
|
+
* @typedef {Object} SubSpec
|
|
72
|
+
* @property {string} name The name of the subscript, e.g., "A1".
|
|
73
|
+
* @property {number} index The subscript index relative to its parent dimension.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Return an array containing all subscript specs in the given dimension. If
|
|
78
|
+
* the given ID is a subscript, it will return a single-element array with that
|
|
79
|
+
* subscript name and index.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} subOrDimId A subscript or dimension ID (e.g., '_a1', '_dima').
|
|
82
|
+
* @returns {SubSpec[]} A (possibly nested) array of `SubSpec` objects.
|
|
83
|
+
*/
|
|
84
|
+
function expandSubSpecs(subOrDimId) {
|
|
85
|
+
if (isDimension(subOrDimId)) {
|
|
86
|
+
// Get the object for the dimension
|
|
87
|
+
const dimObj = sub(subOrDimId)
|
|
88
|
+
|
|
89
|
+
// The dimension may contain a mix of individual subscripts (indices) and/or subdimensions,
|
|
90
|
+
// so recursively expand them
|
|
91
|
+
return dimObj.value.map(expandSubSpecs)
|
|
92
|
+
} else {
|
|
93
|
+
// This is an individual subscript (index), so return its name and index value
|
|
94
|
+
// XXX: Currently, the object returned by `sub` will not include the `modelName`
|
|
95
|
+
// for subscripts (it's only defined for dimensions?), so if we don't have the
|
|
96
|
+
// `modelName`, find it using the parent dimension object. This could be avoided
|
|
97
|
+
// if subscript objects maintained their original model name.
|
|
98
|
+
const subObj = sub(subOrDimId)
|
|
99
|
+
const dimSubNames = expandSubNamesForDim(subObj.family).flat(Infinity)
|
|
100
|
+
const subName = dimSubNames[subObj.value]
|
|
101
|
+
if (subName === undefined) {
|
|
102
|
+
throw new Error(`Failed to resolve name of subscript ${subOrDimId} in dimension ${subObj.family}`)
|
|
103
|
+
}
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
name: subName,
|
|
107
|
+
index: subObj.value
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Return an array containing all subscript (index) names in the given dimension,
|
|
115
|
+
* expanding subdimensions as needed. If the given ID is a subscript, it will return
|
|
116
|
+
* single-element array with that subscript name.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} dimId A dimension ID (e.g., '_dima').
|
|
119
|
+
* @returns {string[]} A (possibly nested) array of subscript names (e.g., 'A1').
|
|
120
|
+
*/
|
|
121
|
+
function expandSubNamesForDim(dimId) {
|
|
122
|
+
if (!isDimension(dimId)) {
|
|
123
|
+
throw new Error('expandSubNames should only be called with a dimension ID')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get the object for the dimension
|
|
127
|
+
const dimObj = sub(dimId)
|
|
128
|
+
|
|
129
|
+
// The dimension may contain a mix of individual subscripts (indices) and/or
|
|
130
|
+
// subdimensions, so recursively expand them
|
|
131
|
+
return dimObj.modelValue.map(subOrDimName => {
|
|
132
|
+
const subOrDimId = canonicalName(subOrDimName)
|
|
133
|
+
if (isDimension(subOrDimId)) {
|
|
134
|
+
return expandSubNamesForDim(subOrDimId)
|
|
135
|
+
} else {
|
|
136
|
+
return [subOrDimName]
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
package/src/model/model.js
CHANGED
|
@@ -2,7 +2,7 @@ import B from 'bufx'
|
|
|
2
2
|
import yaml from 'js-yaml'
|
|
3
3
|
import * as R from 'ramda'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { canonicalVensimName, decanonicalize, isIterable, strlist, vlog, vsort } from '../_shared/helpers.js'
|
|
6
6
|
import {
|
|
7
7
|
addIndex,
|
|
8
8
|
allAliases,
|
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
sub,
|
|
14
14
|
subscriptFamilies
|
|
15
15
|
} from '../_shared/subscript.js'
|
|
16
|
+
import { cName } from '../_shared/var-names.js'
|
|
16
17
|
|
|
18
|
+
import { expandVar } from './expand-var-instances.js'
|
|
17
19
|
import { readEquation } from './read-equations.js'
|
|
18
20
|
import { readDimensionDefs } from './read-subscripts.js'
|
|
19
21
|
import { readVariables } from './read-variables.js'
|
|
@@ -60,12 +62,12 @@ function resetModelState() {
|
|
|
60
62
|
* TODO: FIX TYPE
|
|
61
63
|
* @param {*} parsedModel The parsed model structure.
|
|
62
64
|
* @param {*} spec The parsed `spec.json` object.
|
|
63
|
-
* @param {Map<string, any>} extData The map of datasets from external `.dat` files.
|
|
64
|
-
* @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA`
|
|
65
|
+
* @param {Map<string, any>} [extData] The map of datasets from external `.dat` files.
|
|
66
|
+
* @param {Map<string, any>} [directData] The mapping of dataset name used in a `GET DIRECT DATA`
|
|
65
67
|
* call (e.g., `?data`) to the tabular data contained in the loaded data file.
|
|
66
|
-
* @param {string} modelDirname The path to the directory containing the model (used for resolving data
|
|
68
|
+
* @param {string} [modelDirname] The path to the directory containing the model (used for resolving data
|
|
67
69
|
* files for `GET DIRECT SUBSCRIPT`).
|
|
68
|
-
* @param {*} opts An optional object used by tests to stop the read process after a specific phase.
|
|
70
|
+
* @param {*} [opts] An optional object used by tests to stop the read process after a specific phase.
|
|
69
71
|
*/
|
|
70
72
|
function read(parsedModel, spec, extData, directData, modelDirname, opts) {
|
|
71
73
|
// Some arrays need to be separated into variables with individual indices to
|
|
@@ -425,7 +427,13 @@ function removeUnusedVariables(spec) {
|
|
|
425
427
|
}
|
|
426
428
|
|
|
427
429
|
// Filter out unneeded variables so we're left with the minimal set of variables to emit
|
|
428
|
-
|
|
430
|
+
const filteredVariables = R.filter(v => referencedVarNames.has(v.varName), variables)
|
|
431
|
+
// TODO: Note that we reuse the same `variables` array instance here instead of reassigning
|
|
432
|
+
// to it because some code (like in `code-gen/expand-var-names.js` and in some tests) uses
|
|
433
|
+
// the `variables` array (from module-level storage) directly. We need to fix those uses
|
|
434
|
+
// to use accessors to avoid these subtle issues.
|
|
435
|
+
variables.length = 0
|
|
436
|
+
variables.push(...filteredVariables)
|
|
429
437
|
|
|
430
438
|
// Rebuild the variables-by-name map
|
|
431
439
|
variablesByName.clear()
|
|
@@ -732,28 +740,6 @@ function vensimName(cVarName) {
|
|
|
732
740
|
}
|
|
733
741
|
return result
|
|
734
742
|
}
|
|
735
|
-
function cName(vensimVarName) {
|
|
736
|
-
// Convert a Vensim variable name to a C name.
|
|
737
|
-
// This function requires model analysis to be completed first when the variable has subscripts.
|
|
738
|
-
|
|
739
|
-
// Split the variable name from the subscripts
|
|
740
|
-
let matches = vensimVarName.match(/([^[]+)(?:\[([^\]]+)\])?/)
|
|
741
|
-
if (!matches) {
|
|
742
|
-
throw new Error(`Invalid variable name '${vensimVarName}' found when converting to C representation`)
|
|
743
|
-
}
|
|
744
|
-
let cVarName = canonicalName(matches[1])
|
|
745
|
-
if (matches[2]) {
|
|
746
|
-
// The variable name includes subscripts, so split them into individual IDs
|
|
747
|
-
let cSubIds = matches[2].split(',').map(x => canonicalName(x))
|
|
748
|
-
// If a subscript is an index, convert it to an index number to match Vensim data exports
|
|
749
|
-
let cSubIdParts = cSubIds.map(cSubId => {
|
|
750
|
-
return isIndex(cSubId) ? `[${sub(cSubId).value}]` : `[${cSubId}]`
|
|
751
|
-
})
|
|
752
|
-
// Append the subscript parts to the base variable name to create the full reference
|
|
753
|
-
cVarName += cSubIdParts.join('')
|
|
754
|
-
}
|
|
755
|
-
return cVarName
|
|
756
|
-
}
|
|
757
743
|
function isInputVar(varName) {
|
|
758
744
|
// Return true if the given variable (in canonical form) is included in the list of
|
|
759
745
|
// input variables in the spec file.
|
|
@@ -1105,12 +1091,13 @@ function allListedVars() {
|
|
|
1105
1091
|
}
|
|
1106
1092
|
|
|
1107
1093
|
// The order of execution/evaluation in the generated model is:
|
|
1108
|
-
// initConstants (vars of type `const` only)
|
|
1109
|
-
// initLookups (vars of type `lookup` only)
|
|
1110
|
-
// initData (vars of type `data` only)
|
|
1111
|
-
// initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1094
|
+
// initConstants (vars of type `const` only; called for t=0 only)
|
|
1095
|
+
// initLookups (vars of type `lookup` only; called for t=0 only)
|
|
1096
|
+
// initData (vars of type `data` only; called for t=0 only)
|
|
1097
|
+
// initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars;
|
|
1098
|
+
// called for t=0 only)
|
|
1099
|
+
// evalAux (vars of type `aux` only; called for t>=0)
|
|
1100
|
+
// evalLevels (vars of type `level` only; called before `evalAux` for t>0)
|
|
1114
1101
|
// So to make the ordering in the listing better match the order of evaluation,
|
|
1115
1102
|
// we emit variables in the above order, but filter to avoid having duplicates.
|
|
1116
1103
|
addUnique(constVars())
|
|
@@ -1151,7 +1138,7 @@ function varIndexInfoMap() {
|
|
|
1151
1138
|
|
|
1152
1139
|
// Get the set of unique variable names, and assign a 1-based index to each.
|
|
1153
1140
|
// This matches the index number used in `storeOutput` and `setLookup` in the
|
|
1154
|
-
// generated C/JS code
|
|
1141
|
+
// generated C/JS code.
|
|
1155
1142
|
const infoMap = new Map()
|
|
1156
1143
|
let varIndex = 1
|
|
1157
1144
|
for (const v of sortedVars) {
|
|
@@ -1209,6 +1196,73 @@ function jsonList() {
|
|
|
1209
1196
|
}
|
|
1210
1197
|
}
|
|
1211
1198
|
|
|
1199
|
+
//
|
|
1200
|
+
// We include a `varInstances` object in the generated JSON listing that
|
|
1201
|
+
// includes an array of expanded variable items (one item for every "instance"
|
|
1202
|
+
// of a variable, including subscripted variables) in the same order that they
|
|
1203
|
+
// are evaluated (assigned) in the generated model.
|
|
1204
|
+
//
|
|
1205
|
+
// Each object contains the following minimal set of fields that are needed
|
|
1206
|
+
// for accessing the data for a single variable instance at runtime:
|
|
1207
|
+
// varId (e.g., '_variable_name')
|
|
1208
|
+
// varName (e.g., 'Variable Name')
|
|
1209
|
+
// varType ('const', 'data', 'lookup', 'initial', 'level', 'aux')
|
|
1210
|
+
// varIndex
|
|
1211
|
+
// subscriptIndices
|
|
1212
|
+
//
|
|
1213
|
+
// The order of execution/evaluation in the generated model is:
|
|
1214
|
+
// initConstants (vars of type `const` only, called for t=0 only)
|
|
1215
|
+
// initLookups (vars of type `lookup` only, called for t=0 only)
|
|
1216
|
+
// initData (vars of type `data` only, called for t=0 only)
|
|
1217
|
+
// initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars,
|
|
1218
|
+
// called for t=0 only)
|
|
1219
|
+
// evalAux (vars of type `aux` only; called for t>=0)
|
|
1220
|
+
// evalLevels (vars of type `level` only; called before `evalAux` for t>0)
|
|
1221
|
+
//
|
|
1222
|
+
function expandedVarItems(vars) {
|
|
1223
|
+
const expandedVars = []
|
|
1224
|
+
|
|
1225
|
+
for (const v of vars) {
|
|
1226
|
+
// Filter out variables that are generated/used internally
|
|
1227
|
+
if (v.includeInOutput === false) {
|
|
1228
|
+
continue
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const varInstances = expandVar(v)
|
|
1232
|
+
for (const { varName, subscriptIndices } of varInstances) {
|
|
1233
|
+
const varId = canonicalVensimName(varName)
|
|
1234
|
+
const varItem = {
|
|
1235
|
+
varId,
|
|
1236
|
+
varName,
|
|
1237
|
+
varType: v.varType
|
|
1238
|
+
}
|
|
1239
|
+
const varInfo = infoMap.get(v.varName)
|
|
1240
|
+
if (varInfo) {
|
|
1241
|
+
varItem.varIndex = varInfo.varIndex
|
|
1242
|
+
if (subscriptIndices?.length > 0) {
|
|
1243
|
+
varItem.subscriptIndices = subscriptIndices
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
expandedVars.push(varItem)
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return expandedVars
|
|
1251
|
+
}
|
|
1252
|
+
const expandedConstants = expandedVarItems(constVars())
|
|
1253
|
+
const expandedLookupVars = expandedVarItems(lookupVars())
|
|
1254
|
+
const expandedDataVars = expandedVarItems(dataVars())
|
|
1255
|
+
// The special exogenous `Time` variable may have already been removed by
|
|
1256
|
+
// `removeUnusedVariables` if it is not referenced explicitly in the model,
|
|
1257
|
+
// so we will only include it in the listing if it is defined here. Note
|
|
1258
|
+
// that `_time` is set to `_initial_time` as the first step in the
|
|
1259
|
+
// `initLevels` function, which is why it is included in the "init" group.
|
|
1260
|
+
const timeVar = varWithName('_time')
|
|
1261
|
+
const specialInitVars = timeVar ? [timeVar] : []
|
|
1262
|
+
const expandedInitVars = expandedVarItems([...specialInitVars, ...initVars()])
|
|
1263
|
+
const expandedLevelVars = expandedVarItems(levelVars())
|
|
1264
|
+
const expandedAuxVars = expandedVarItems(auxVars())
|
|
1265
|
+
|
|
1212
1266
|
// Derive minimal versions of the full arrays; these only contain the minimal
|
|
1213
1267
|
// subset of fields that are needed by the `ModelListing` class from the
|
|
1214
1268
|
// runtime package. The property names in the minimal objects are slightly
|
|
@@ -1245,7 +1299,15 @@ function jsonList() {
|
|
|
1245
1299
|
cachedJsonList = {
|
|
1246
1300
|
full: {
|
|
1247
1301
|
dimensions: sortedFullDims,
|
|
1248
|
-
variables: sortedFullVars
|
|
1302
|
+
variables: sortedFullVars,
|
|
1303
|
+
varInstances: {
|
|
1304
|
+
constants: expandedConstants,
|
|
1305
|
+
lookupVars: expandedLookupVars,
|
|
1306
|
+
dataVars: expandedDataVars,
|
|
1307
|
+
initVars: expandedInitVars,
|
|
1308
|
+
levelVars: expandedLevelVars,
|
|
1309
|
+
auxVars: expandedAuxVars
|
|
1310
|
+
}
|
|
1249
1311
|
},
|
|
1250
1312
|
minimal: {
|
|
1251
1313
|
dimensions: sortedMinimalDims,
|
|
@@ -1261,7 +1323,6 @@ export default {
|
|
|
1261
1323
|
addVariable,
|
|
1262
1324
|
allVars,
|
|
1263
1325
|
auxVars,
|
|
1264
|
-
cName,
|
|
1265
1326
|
constVars,
|
|
1266
1327
|
dataVars,
|
|
1267
1328
|
expansionFlags,
|
package/src/model/toposort.js
CHANGED
|
@@ -8,6 +8,7 @@ import { parseVensimModel } from '@sdeverywhere/parse'
|
|
|
8
8
|
import { readXlsx } from './_shared/helpers.js'
|
|
9
9
|
import { readDat } from './_shared/read-dat.js'
|
|
10
10
|
import { printSubscripts, yamlSubsList } from './_shared/subscript.js'
|
|
11
|
+
import { cName } from './_shared/var-names.js'
|
|
11
12
|
import Model from './model/model.js'
|
|
12
13
|
import { getDirectSubscripts } from './model/read-subscripts.js'
|
|
13
14
|
import { generateCode } from './generate/gen-code.js'
|
|
@@ -120,7 +121,7 @@ export function printNames(namesPathname, operation) {
|
|
|
120
121
|
for (let line of lines) {
|
|
121
122
|
if (line.length > 0) {
|
|
122
123
|
if (operation === 'to-c') {
|
|
123
|
-
B.emitLine(
|
|
124
|
+
B.emitLine(cName(line))
|
|
124
125
|
} else {
|
|
125
126
|
B.emitLine(Model.vensimName(line))
|
|
126
127
|
}
|