@sdeverywhere/compile 0.7.24 → 0.7.26

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 CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.24",
3
+ "version": "0.7.26",
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
8
  "@sdeverywhere/parse": "^0.1.2",
9
- "bufx": "^1.0.5",
10
9
  "byline": "^5.0.0",
11
10
  "csv-parse": "^5.3.3",
12
- "js-yaml": "^3.13.1",
13
11
  "ramda": "^0.27.0",
12
+ "strip-bom": "^5.0.0",
14
13
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
15
14
  },
16
15
  "author": "Climate Interactive",
@@ -0,0 +1,66 @@
1
+ import * as fs from 'node:fs'
2
+ import stripBom from 'strip-bom'
3
+
4
+ // Numeric value of a string or number
5
+ let num = x => (typeof x === 'number' ? x : Number.parseFloat(x))
6
+ // Split a string into lines that may have Windows, Unix, or old Mac line endings.
7
+ let lines = s => s.split(/\r\n|\n|\r/)
8
+ // Print a string to the console
9
+ let print = s => {
10
+ console.log(s)
11
+ }
12
+ // Read a UTF-8 file into a string. Strip the BOM if present.
13
+ let read = pathname => stripBom(fs.readFileSync(pathname, 'utf8'))
14
+ // Write a string to a UTF-8 file
15
+ let write = (s, pathname) => {
16
+ fs.writeFileSync(pathname, s, { encoding: 'utf8' })
17
+ }
18
+
19
+ // Output buffer
20
+ let bufs = { _: '' }
21
+ // Open a buffer for writing
22
+ let open = channel => (bufs[channel] = '')
23
+ // Emit a string to a buffer
24
+ let emit = (a, channel = null) => {
25
+ channel = channel || '_'
26
+ bufs[channel] += a
27
+ }
28
+ // Emit a string to a buffer terminated by a newline
29
+ let emitLine = (a, channel = null) => {
30
+ channel = channel || '_'
31
+ bufs[channel] += a + '\n'
32
+ }
33
+ // Print a buffer to the console
34
+ let printBuf = (channel = null) => {
35
+ channel = channel || '_'
36
+ print(bufs[channel])
37
+ }
38
+ // Write a buffer to a file
39
+ let writeBuf = (pathname, channel = null) => {
40
+ channel = channel || '_'
41
+ write(bufs[channel], pathname)
42
+ }
43
+ // Get buffer contents as a string
44
+ let getBuf = (channel = null) => {
45
+ channel = channel || '_'
46
+ return bufs[channel]
47
+ }
48
+ // Clear a buffer
49
+ let clearBuf = (channel = null) => {
50
+ channel = channel || '_'
51
+ bufs[channel] = ''
52
+ }
53
+
54
+ export default {
55
+ clearBuf,
56
+ emit,
57
+ emitLine,
58
+ getBuf,
59
+ lines,
60
+ num,
61
+ open,
62
+ printBuf,
63
+ read,
64
+ write,
65
+ writeBuf
66
+ }
@@ -1,9 +1,9 @@
1
1
  import * as fs from 'node:fs'
2
2
  import util from 'util'
3
- import B from 'bufx'
4
3
  import { parse as parseCsv } from 'csv-parse/sync'
5
4
  import * as R from 'ramda'
6
5
  import XLSX from 'xlsx'
6
+ import B from './bufx.js'
7
7
 
8
8
  import { canonicalId, canonicalVarId } from '@sdeverywhere/parse'
9
9
 
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs'
2
- import B from 'bufx'
3
2
  import byline from 'byline'
4
3
  import * as R from 'ramda'
5
4
 
5
+ import B from './bufx.js'
6
6
  import { canonicalVensimName } from './helpers.js'
7
7
 
8
8
  /**
@@ -1,7 +1,6 @@
1
1
  import util from 'util'
2
- import B from 'bufx'
3
- import yaml from 'js-yaml'
4
2
  import * as R from 'ramda'
3
+ import B from './bufx.js'
5
4
  import { canonicalName, vlog } from './helpers.js'
6
5
 
7
6
  // A subscript is a dimension or an index.
@@ -107,7 +106,7 @@ export function sub(name) {
107
106
  let result
108
107
  try {
109
108
  result = subscripts.get(name)
110
- } catch (e) {
109
+ } catch (_) {
111
110
  console.error(`sub name ${name} not found`)
112
111
  }
113
112
  return result
@@ -218,21 +217,6 @@ export function printSubscripts() {
218
217
  }
219
218
  return B.getBuf()
220
219
  }
221
- export function yamlSubsList() {
222
- let subs = {}
223
- for (let [k, v] of subscripts) {
224
- subs[k] = v
225
- }
226
- return yaml.safeDump(subs)
227
- }
228
- export function loadSubscriptsFromYaml(yamlSubs) {
229
- // Load the subscripts map from subscripts serialized to a YAML file by yamlSubsList.
230
- // This function should be called instead of adding subscripts through the constructor.
231
- let subs = yaml.safeLoad(yamlSubs)
232
- for (const k in subs) {
233
- subscripts.set(k, subs[k])
234
- }
235
- }
236
220
  export function extractMarkedDims(subscripts) {
237
221
  // Extract all marked dimensions and update subscripts.
238
222
  let dims = []
@@ -249,7 +233,7 @@ export function subscriptFamilies(subscripts) {
249
233
  // Return a list of the subscript families for each subscript.
250
234
  try {
251
235
  return R.map(subscriptName => sub(subscriptName).family, subscripts)
252
- } catch (e) {
236
+ } catch (_) {
253
237
  console.error(`ERROR: subscript not found in "${subscripts.join(',')}" in subscriptFamilies`)
254
238
  }
255
239
  }
@@ -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 { cartesianProductOf, canonicalName } from '../_shared/helpers.js'
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 variable in the model, sorted alphabetically and expanded to
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 canonical If true, convert names to canonical representation (variable identifiers), otherwise
13
- * return the original name of each variable as it appears in the model.
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(Model.cName, namesForVar(v)))
26
+ return R.concat(a, R.map(cName, varNames))
24
27
  } else {
25
- return R.concat(a, namesForVar(v))
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
+ }
@@ -1,8 +1,7 @@
1
- import B from 'bufx'
2
- import yaml from 'js-yaml'
3
1
  import * as R from 'ramda'
4
2
 
5
- import { canonicalName, decanonicalize, isIterable, /*listConcat,*/ strlist, vlog, vsort } from '../_shared/helpers.js'
3
+ import B from '../_shared/bufx.js'
4
+ import { canonicalVensimName, decanonicalize, isIterable, strlist, vlog, vsort } from '../_shared/helpers.js'
6
5
  import {
7
6
  addIndex,
8
7
  allAliases,
@@ -13,7 +12,9 @@ import {
13
12
  sub,
14
13
  subscriptFamilies
15
14
  } from '../_shared/subscript.js'
15
+ import { cName } from '../_shared/var-names.js'
16
16
 
17
+ import { expandVar } from './expand-var-instances.js'
17
18
  import { readEquation } from './read-equations.js'
18
19
  import { readDimensionDefs } from './read-subscripts.js'
19
20
  import { readVariables } from './read-variables.js'
@@ -60,12 +61,12 @@ function resetModelState() {
60
61
  * TODO: FIX TYPE
61
62
  * @param {*} parsedModel The parsed model structure.
62
63
  * @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`
64
+ * @param {Map<string, any>} [extData] The map of datasets from external `.dat` files.
65
+ * @param {Map<string, any>} [directData] The mapping of dataset name used in a `GET DIRECT DATA`
65
66
  * 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
67
+ * @param {string} [modelDirname] The path to the directory containing the model (used for resolving data
67
68
  * files for `GET DIRECT SUBSCRIPT`).
68
- * @param {*} opts An optional object used by tests to stop the read process after a specific phase.
69
+ * @param {*} [opts] An optional object used by tests to stop the read process after a specific phase.
69
70
  */
70
71
  function read(parsedModel, spec, extData, directData, modelDirname, opts) {
71
72
  // Some arrays need to be separated into variables with individual indices to
@@ -425,7 +426,13 @@ function removeUnusedVariables(spec) {
425
426
  }
426
427
 
427
428
  // Filter out unneeded variables so we're left with the minimal set of variables to emit
428
- variables = R.filter(v => referencedVarNames.has(v.varName), variables)
429
+ const filteredVariables = R.filter(v => referencedVarNames.has(v.varName), variables)
430
+ // TODO: Note that we reuse the same `variables` array instance here instead of reassigning
431
+ // to it because some code (like in `code-gen/expand-var-names.js` and in some tests) uses
432
+ // the `variables` array (from module-level storage) directly. We need to fix those uses
433
+ // to use accessors to avoid these subtle issues.
434
+ variables.length = 0
435
+ variables.push(...filteredVariables)
429
436
 
430
437
  // Rebuild the variables-by-name map
431
438
  variablesByName.clear()
@@ -732,28 +739,6 @@ function vensimName(cVarName) {
732
739
  }
733
740
  return result
734
741
  }
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
742
  function isInputVar(varName) {
758
743
  // Return true if the given variable (in canonical form) is included in the list of
759
744
  // input variables in the spec file.
@@ -960,14 +945,6 @@ function printVarList() {
960
945
  }
961
946
  return B.getBuf()
962
947
  }
963
- function yamlVarList() {
964
- // Print selected properties of all variable objects to a YAML string.
965
- let vars = R.sortBy(
966
- R.prop('refId'),
967
- R.map(v => filterVar(v), variables)
968
- )
969
- return yaml.safeDump(vars)
970
- }
971
948
  function printVar(v) {
972
949
  let nonAtoA = isNonAtoAName(v.varName) ? ' (non-apply-to-all)' : ''
973
950
  B.emitLine(`${v.modelLHS}: ${v.varType}${nonAtoA}`)
@@ -1105,12 +1082,13 @@ function allListedVars() {
1105
1082
  }
1106
1083
 
1107
1084
  // 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
- // evalAux (vars of type `aux` only)
1113
- // evalLevels (vars of type `level` only)
1085
+ // initConstants (vars of type `const` only; called for t=0 only)
1086
+ // initLookups (vars of type `lookup` only; called for t=0 only)
1087
+ // initData (vars of type `data` only; called for t=0 only)
1088
+ // initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars;
1089
+ // called for t=0 only)
1090
+ // evalAux (vars of type `aux` only; called for t>=0)
1091
+ // evalLevels (vars of type `level` only; called before `evalAux` for t>0)
1114
1092
  // So to make the ordering in the listing better match the order of evaluation,
1115
1093
  // we emit variables in the above order, but filter to avoid having duplicates.
1116
1094
  addUnique(constVars())
@@ -1151,7 +1129,7 @@ function varIndexInfoMap() {
1151
1129
 
1152
1130
  // Get the set of unique variable names, and assign a 1-based index to each.
1153
1131
  // This matches the index number used in `storeOutput` and `setLookup` in the
1154
- // generated C/JS code
1132
+ // generated C/JS code.
1155
1133
  const infoMap = new Map()
1156
1134
  let varIndex = 1
1157
1135
  for (const v of sortedVars) {
@@ -1209,6 +1187,73 @@ function jsonList() {
1209
1187
  }
1210
1188
  }
1211
1189
 
1190
+ //
1191
+ // We include a `varInstances` object in the generated JSON listing that
1192
+ // includes an array of expanded variable items (one item for every "instance"
1193
+ // of a variable, including subscripted variables) in the same order that they
1194
+ // are evaluated (assigned) in the generated model.
1195
+ //
1196
+ // Each object contains the following minimal set of fields that are needed
1197
+ // for accessing the data for a single variable instance at runtime:
1198
+ // varId (e.g., '_variable_name')
1199
+ // varName (e.g., 'Variable Name')
1200
+ // varType ('const', 'data', 'lookup', 'initial', 'level', 'aux')
1201
+ // varIndex
1202
+ // subscriptIndices
1203
+ //
1204
+ // The order of execution/evaluation in the generated model is:
1205
+ // initConstants (vars of type `const` only, called for t=0 only)
1206
+ // initLookups (vars of type `lookup` only, called for t=0 only)
1207
+ // initData (vars of type `data` only, called for t=0 only)
1208
+ // initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars,
1209
+ // called for t=0 only)
1210
+ // evalAux (vars of type `aux` only; called for t>=0)
1211
+ // evalLevels (vars of type `level` only; called before `evalAux` for t>0)
1212
+ //
1213
+ function expandedVarItems(vars) {
1214
+ const expandedVars = []
1215
+
1216
+ for (const v of vars) {
1217
+ // Filter out variables that are generated/used internally
1218
+ if (v.includeInOutput === false) {
1219
+ continue
1220
+ }
1221
+
1222
+ const varInstances = expandVar(v)
1223
+ for (const { varName, subscriptIndices } of varInstances) {
1224
+ const varId = canonicalVensimName(varName)
1225
+ const varItem = {
1226
+ varId,
1227
+ varName,
1228
+ varType: v.varType
1229
+ }
1230
+ const varInfo = infoMap.get(v.varName)
1231
+ if (varInfo) {
1232
+ varItem.varIndex = varInfo.varIndex
1233
+ if (subscriptIndices?.length > 0) {
1234
+ varItem.subscriptIndices = subscriptIndices
1235
+ }
1236
+ }
1237
+ expandedVars.push(varItem)
1238
+ }
1239
+ }
1240
+
1241
+ return expandedVars
1242
+ }
1243
+ const expandedConstants = expandedVarItems(constVars())
1244
+ const expandedLookupVars = expandedVarItems(lookupVars())
1245
+ const expandedDataVars = expandedVarItems(dataVars())
1246
+ // The special exogenous `Time` variable may have already been removed by
1247
+ // `removeUnusedVariables` if it is not referenced explicitly in the model,
1248
+ // so we will only include it in the listing if it is defined here. Note
1249
+ // that `_time` is set to `_initial_time` as the first step in the
1250
+ // `initLevels` function, which is why it is included in the "init" group.
1251
+ const timeVar = varWithName('_time')
1252
+ const specialInitVars = timeVar ? [timeVar] : []
1253
+ const expandedInitVars = expandedVarItems([...specialInitVars, ...initVars()])
1254
+ const expandedLevelVars = expandedVarItems(levelVars())
1255
+ const expandedAuxVars = expandedVarItems(auxVars())
1256
+
1212
1257
  // Derive minimal versions of the full arrays; these only contain the minimal
1213
1258
  // subset of fields that are needed by the `ModelListing` class from the
1214
1259
  // runtime package. The property names in the minimal objects are slightly
@@ -1245,7 +1290,15 @@ function jsonList() {
1245
1290
  cachedJsonList = {
1246
1291
  full: {
1247
1292
  dimensions: sortedFullDims,
1248
- variables: sortedFullVars
1293
+ variables: sortedFullVars,
1294
+ varInstances: {
1295
+ constants: expandedConstants,
1296
+ lookupVars: expandedLookupVars,
1297
+ dataVars: expandedDataVars,
1298
+ initVars: expandedInitVars,
1299
+ levelVars: expandedLevelVars,
1300
+ auxVars: expandedAuxVars
1301
+ }
1249
1302
  },
1250
1303
  minimal: {
1251
1304
  dimensions: sortedMinimalDims,
@@ -1261,7 +1314,6 @@ export default {
1261
1314
  addVariable,
1262
1315
  allVars,
1263
1316
  auxVars,
1264
- cName,
1265
1317
  constVars,
1266
1318
  dataVars,
1267
1319
  expansionFlags,
@@ -1287,6 +1339,5 @@ export default {
1287
1339
  varsWithName,
1288
1340
  varWithName,
1289
1341
  varWithRefId,
1290
- vensimName,
1291
- yamlVarList
1342
+ vensimName
1292
1343
  }
@@ -39,7 +39,7 @@ function toposort(nodes, edges) {
39
39
  var nodeRep
40
40
  try {
41
41
  nodeRep = '\n' + node + '\n'
42
- } catch (e) {
42
+ } catch (_) {
43
43
  nodeRep = ''
44
44
  }
45
45
  throw new Error('Found cyclic dependency during toposort:\n' + [...predecessors].join(' →\n') + ' →' + nodeRep)
@@ -1,13 +1,14 @@
1
1
  // Copyright (c) 2022 Climate Interactive / New Venture Fund
2
2
 
3
3
  import path from 'path'
4
- import B from 'bufx'
5
4
 
6
5
  import { parseVensimModel } from '@sdeverywhere/parse'
7
6
 
7
+ import B from './_shared/bufx.js'
8
8
  import { readXlsx } from './_shared/helpers.js'
9
9
  import { readDat } from './_shared/read-dat.js'
10
- import { printSubscripts, yamlSubsList } from './_shared/subscript.js'
10
+ import { printSubscripts } 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'
@@ -20,7 +21,7 @@ import { generateCode } from './generate/gen-code.js'
20
21
  * - If `operations` has 'generateC', the generated C code will be written to `buildDir`.
21
22
  * - If `operations` has 'generateJS', the generated JS code will be written to `buildDir`.
22
23
  * - If `operations` has 'printVarList', variables and subscripts will be written to
23
- * txt, yaml, and json files under `buildDir`.
24
+ * txt and json files under `buildDir`.
24
25
  * - If `operations` has 'printRefIdTest', reference identifiers will be printed to the console.
25
26
  * - If `operations` has 'convertNames', no output will be generated, but the results of model
26
27
  * analysis will be available.
@@ -91,10 +92,6 @@ export async function parseAndGenerate(input, spec, operations, modelDirname, mo
91
92
  writeOutput(`${modelName}_vars.txt`, Model.printVarList())
92
93
  // Write subscripts to a text file.
93
94
  writeOutput(`${modelName}_subs.txt`, printSubscripts())
94
- // Write variables to a YAML file.
95
- writeOutput(`${modelName}_vars.yaml`, Model.yamlVarList())
96
- // Write subscripts to a YAML file.
97
- writeOutput(`${modelName}_subs.yaml`, yamlSubsList())
98
95
  // Write variables and subscripts to a JSON file.
99
96
  const jsonList = Model.jsonList()
100
97
  writeOutput(`${modelName}.json`, JSON.stringify(jsonList.full, null, 2))
@@ -120,7 +117,7 @@ export function printNames(namesPathname, operation) {
120
117
  for (let line of lines) {
121
118
  if (line.length > 0) {
122
119
  if (operation === 'to-c') {
123
- B.emitLine(Model.cName(line))
120
+ B.emitLine(cName(line))
124
121
  } else {
125
122
  B.emitLine(Model.vensimName(line))
126
123
  }