@sdeverywhere/compile 0.7.17 → 0.7.19

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.
@@ -10,11 +10,12 @@ import { pointsString } from './gen-lookup-from-points.js'
10
10
  * @param {*} variable The `Variable` instance to process.
11
11
  * @param {'decl' | 'init-lookups'} mode The code generation mode.
12
12
  * @param {Map<string, any>} extData The map of datasets from external `.dat` files.
13
- * @param {string} varLhs The C code for the LHS variable reference.
14
- * @return {string[]} An array of strings containing the generated C code for the variable,
13
+ * @param {string} varLhs The C/JS code for the LHS variable reference.
14
+ * @param {'c' | 'js'} outFormat The output format.
15
+ * @return {string[]} An array of strings containing the generated C/JS code for the variable,
15
16
  * one string per line of code.
16
17
  */
17
- export function generateLookupsFromExternalData(variable, mode, extData, varLhs) {
18
+ export function generateLookupsFromExternalData(variable, mode, extData, varLhs, outFormat) {
18
19
  if (mode !== 'decl' && mode !== 'init-lookups') {
19
20
  throw new Error(`Invalid code gen mode '${mode}' for data variable ${variable.modelLHS}`)
20
21
  }
@@ -37,10 +38,24 @@ export function generateLookupsFromExternalData(variable, mode, extData, varLhs)
37
38
  // In decl mode, declare a static data array that will be used to create the associated `Lookup`
38
39
  // at init time
39
40
  const points = pointsString(Array.from(data.entries()))
40
- return `double ${dataName}[${data.size * 2}] = { ${points} };`
41
+ switch (outFormat) {
42
+ case 'c':
43
+ return `double ${dataName}[${data.size * 2}] = { ${points} };`
44
+ case 'js':
45
+ return `const ${dataName} = [${points}];`
46
+ default:
47
+ throw new Error(`Unhandled output format '${outFormat}'`)
48
+ }
41
49
  } else if (mode === 'init-lookups') {
42
50
  // In init mode, create the `Lookup`, passing in a pointer to the static data array declared in decl mode.
43
- return ` ${lhs} = __new_lookup(${data.size}, /*copy=*/false, ${dataName});`
51
+ switch (outFormat) {
52
+ case 'c':
53
+ return ` ${lhs} = __new_lookup(${data.size}, /*copy=*/false, ${dataName});`
54
+ case 'js':
55
+ return ` ${lhs} = fns.createLookup(${data.size}, ${dataName});`
56
+ default:
57
+ throw new Error(`Unhandled output format '${outFormat}'`)
58
+ }
44
59
  } else {
45
60
  return []
46
61
  }
@@ -8,12 +8,13 @@ import { isDimension, isTrivialDimension, sub } from '../_shared/subscript.js'
8
8
  * @param {'decl' | 'init-lookups'} mode The code generation mode.
9
9
  * @param {boolean} copy If false, a static data array will be used (good for larger data sets).
10
10
  * If true, the data will inlined and copied when initializing the lookup (good for smaller data sets).
11
- * @param {string} varLhs The C code for the LHS variable reference.
11
+ * @param {string} varLhs The C/JS code for the LHS variable reference.
12
12
  * @param {LoopIndexVars} loopIndexVars The loop index state.
13
- * @return {string[]} An array of strings containing the generated C code for the variable,
13
+ * @param {'c' | 'js'} outFormat The output format.
14
+ * @return {string[]} An array of strings containing the generated C/JS code for the variable,
14
15
  * one string per line of code.
15
16
  */
16
- export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndexVars) {
17
+ export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndexVars, outFormat) {
17
18
  if (variable.points.length === 0) {
18
19
  throw new Error(`Empty lookup data array for ${variable.modelLHS}`)
19
20
  }
@@ -26,7 +27,14 @@ export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndex
26
27
  } else if (mode === 'init-lookups') {
27
28
  // In init mode, generate a new lookup using the data points from the variable
28
29
  const points = pointsString(variable.points)
29
- return [` ${varLhs} = __new_lookup(${variable.points.length}, /*copy=*/true, (double[]){ ${points} });`]
30
+ switch (outFormat) {
31
+ case 'c':
32
+ return [` ${varLhs} = __new_lookup(${variable.points.length}, /*copy=*/true, (double[]){ ${points} });`]
33
+ case 'js':
34
+ return [` ${varLhs} = fns.createLookup(${variable.points.length}, [${points}]);`]
35
+ default:
36
+ throw new Error(`Unhandled output format '${outFormat}'`)
37
+ }
30
38
  }
31
39
  } else {
32
40
  // Construct the name of the data array, which is based on the associated lookup var name,
@@ -38,11 +46,25 @@ export function generateLookupFromPoints(variable, mode, copy, varLhs, loopIndex
38
46
  // the data in memory, and seems to perform much better when compiled to wasm when compared to the
39
47
  // previous approach that used varargs + copying, especially on constrained (e.g. iOS) devices.
40
48
  const points = pointsString(variable.points)
41
- return [`double ${dataName}[${variable.points.length * 2}] = { ${points} };`]
49
+ switch (outFormat) {
50
+ case 'c':
51
+ return [`double ${dataName}[${variable.points.length * 2}] = { ${points} };`]
52
+ case 'js':
53
+ return [`const ${dataName} = [${points}];`]
54
+ default:
55
+ throw new Error(`Unhandled output format '${outFormat}'`)
56
+ }
42
57
  } else if (mode === 'init-lookups') {
43
58
  // In init mode, create the `Lookup`, passing in a pointer to the static data array declared earlier.
44
59
  // TODO: Make use of the lookup range
45
- return [` ${varLhs} = __new_lookup(${variable.points.length}, /*copy=*/false, ${dataName});`]
60
+ switch (outFormat) {
61
+ case 'c':
62
+ return [` ${varLhs} = __new_lookup(${variable.points.length}, /*copy=*/false, ${dataName});`]
63
+ case 'js':
64
+ return [` ${varLhs} = fns.createLookup(${variable.points.length}, ${dataName});`]
65
+ default:
66
+ throw new Error(`Unhandled output format '${outFormat}'`)
67
+ }
46
68
  }
47
69
  }
48
70
 
package/src/index.js CHANGED
@@ -3,6 +3,41 @@
3
3
  export { canonicalName } from './_shared/helpers.js'
4
4
  export { readDat } from './_shared/read-dat.js'
5
5
  export { preprocessModel } from './preprocess/preprocessor.js'
6
- export { parseModel } from './parse/parser.js'
7
- export { generateCode } from './generate/code-gen.js'
8
- export { parseAndGenerate, printNames } from './parse-and-generate.js'
6
+ export { generateCode } from './generate/gen-code.js'
7
+ export { parseAndGenerate, parseModel, printNames } from './parse-and-generate.js'
8
+
9
+ import { resetHelperState } from './_shared/helpers.js'
10
+ import { resetSubscriptsAndDimensions } from './_shared/subscript.js'
11
+ import Model from './model/model.js'
12
+ import { parseModel } from './parse-and-generate.js'
13
+
14
+ /**
15
+ * @hidden This is not yet part of the public API; it is exposed only for use
16
+ * in the experimental playground app.
17
+ */
18
+ export function resetState() {
19
+ // XXX: These steps are needed due to subs/dims and variables being in module-level storage
20
+ resetHelperState()
21
+ resetSubscriptsAndDimensions()
22
+ Model.resetModelState()
23
+ }
24
+
25
+ /**
26
+ * @hidden This is not yet part of the public API; it is exposed only for use
27
+ * in the experimental playground app.
28
+ */
29
+ export function parseInlineVensimModel(mdlContent /*: string*/, modelDir /*?: string*/) /*: ParsedModel*/ {
30
+ // For tests that parse inline model text, in the case of the legacy parser, don't run
31
+ // the preprocess step, and in the case of the new parser (which implicitly runs the
32
+ // preprocess step), don't sort the definitions. This makes it easier to do apples
33
+ // to apples comparisons on the outputs from the two parser implementations.
34
+ return parseModel(mdlContent, modelDir, { sort: false })
35
+ }
36
+
37
+ /**
38
+ * @hidden This is not yet part of the public API; it is exposed only for use
39
+ * in the experimental playground app.
40
+ */
41
+ export function getModelListing() /*: string*/ {
42
+ return Model.jsonList()
43
+ }
@@ -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 { canonicalName, decanonicalize, isIterable, listConcat, strlist, vlog, vsort } from '../_shared/helpers.js'
5
+ import { canonicalName, decanonicalize, isIterable, /*listConcat,*/ strlist, vlog, vsort } from '../_shared/helpers.js'
6
6
  import {
7
7
  addIndex,
8
8
  allAliases,
@@ -14,22 +14,20 @@ import {
14
14
  sub,
15
15
  subscriptFamilies
16
16
  } from '../_shared/subscript.js'
17
- import { createParser } from '../parse/parser.js'
18
17
 
19
- import EquationReader from './equation-reader.js'
20
18
  import { readEquation } from './read-equations.js'
21
19
  import { readDimensionDefs } from './read-subscripts.js'
22
- import { readVariables as readVariables2 } from './read-variables.js'
20
+ import { readVariables } from './read-variables.js'
23
21
  import { reduceVariables } from './reduce-variables.js'
24
- import SubscriptRangeReader from './subscript-range-reader.js'
25
22
  import toposort from './toposort.js'
26
- import VarNameReader from './var-name-reader.js'
27
23
  import Variable from './variable.js'
28
- import VariableReader from './variable-reader.js'
29
24
 
30
25
  let variables = []
31
26
  let inputVars = []
32
27
  let constantExprs = new Map()
28
+ let cachedSortedVarsByType = new Map()
29
+ let cachedVarIndexInfo
30
+ let cachedJsonList
33
31
 
34
32
  // Also keep variables in a map (with `varName` as key) for faster lookup
35
33
  const variablesByName = new Map()
@@ -49,6 +47,9 @@ function resetModelState() {
49
47
  variablesByName.clear()
50
48
  constantExprs.clear()
51
49
  nonAtoANames = Object.create(null)
50
+ cachedSortedVarsByType.clear()
51
+ cachedVarIndexInfo = undefined
52
+ cachedJsonList = undefined
52
53
  }
53
54
 
54
55
  /**
@@ -73,32 +74,22 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) {
73
74
  let specialSeparationDims = spec.specialSeparationDims
74
75
 
75
76
  // Dimensions must be defined before reading variables that use them.
76
- if (parsedModel.kind === 'vensim-legacy') {
77
- readSubscriptRanges(parsedModel.parseTree, modelDirname)
78
- } else {
79
- readDimensionDefs(parsedModel, modelDirname)
80
- }
77
+ readDimensionDefs(parsedModel, modelDirname)
81
78
  if (opts?.stopAfterReadSubscripts) return
82
79
  resolveDimensions(spec.dimensionFamilies)
83
80
  if (opts?.stopAfterResolveSubscripts) return
84
81
 
85
82
  // Read variables from the model parse tree.
86
- if (parsedModel.kind === 'vensim-legacy') {
87
- // TODO: directData is actually unused in VariableReader
88
- readVariables(parsedModel.parseTree, specialSeparationDims, directData)
89
- } else {
90
- // Read the variables
91
- const vars = readVariables2(parsedModel, specialSeparationDims)
83
+ const vars = readVariables(parsedModel, specialSeparationDims)
92
84
 
93
- // Include a placeholder variable for the exogenous `Time` variable
94
- const timeVar = new Variable(null)
95
- timeVar.modelLHS = 'Time'
96
- timeVar.varName = '_time'
97
- vars.push(timeVar)
85
+ // Include a placeholder variable for the exogenous `Time` variable
86
+ const timeVar = new Variable()
87
+ timeVar.modelLHS = 'Time'
88
+ timeVar.varName = '_time'
89
+ vars.push(timeVar)
98
90
 
99
- // Add the variables to the `Model`
100
- vars.forEach(addVariable)
101
- }
91
+ // Add the variables to the `Model`
92
+ vars.forEach(addVariable)
102
93
  if (opts?.stopAfterReadVariables) return
103
94
 
104
95
  if (spec) {
@@ -121,7 +112,7 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) {
121
112
  if (opts?.stopAfterAnalyze) return
122
113
 
123
114
  // Check that all input and output vars in the spec actually exist in the model.
124
- checkSpecVars(spec, extData)
115
+ checkSpecVars(spec)
125
116
 
126
117
  // Remove variables that are not referenced by an input or output variable.
127
118
  removeUnusedVariables(spec)
@@ -130,22 +121,6 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) {
130
121
  resolveDuplicateDeclarations()
131
122
  }
132
123
 
133
- /**
134
- * Read subscript ranges from the given model.
135
- *
136
- * Note that this function currently does not return anything and instead stores the parsed subscript
137
- * range definitions in the `subscript` module.
138
- *
139
- * @param {import('../parse/parser.js').VensimModelParseTree} parseTree The Vensim parse tree.
140
- * @param {string} modelDirname The path to the directory containing the model (used for resolving data
141
- * files for `GET DIRECT SUBSCRIPT`).
142
- */
143
- function readSubscriptRanges(parseTree, modelDirname) {
144
- // Read subscript ranges from the model.
145
- let subscriptRangeReader = new SubscriptRangeReader(modelDirname)
146
- subscriptRangeReader.visitModel(parseTree)
147
- }
148
-
149
124
  /**
150
125
  * Process the previously read subscript/dimension definitions (stored in the `subscript` module) to
151
126
  * resolve aliases, families, and indices.
@@ -289,32 +264,6 @@ function resolveDimensions(dimensionFamilies) {
289
264
  }
290
265
  }
291
266
 
292
- /**
293
- * Read equations from the given model and generate `Variable` instances for all variables that
294
- * are encountered while parsing.
295
- *
296
- * Note that this function currently does not return anything and instead stores the parsed
297
- * variable definitions in the `model` module.
298
- *
299
- * @param {import('../parse/parser.js').VensimModelParseTree} tree The Vensim parse tree.
300
- * @param {Object.<string, string>} specialSeparationDims The variable names that need to be
301
- * separated because of circular references. A mapping from "C" variable name to "C" dimension
302
- * name to separate on.
303
- * @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA`
304
- * call (e.g., `?data`) to the tabular data contained in the loaded data file.
305
- */
306
- function readVariables(tree, specialSeparationDims, directData) {
307
- // Read all variables in the model parse tree.
308
- // This populates the variables table with basic information for each variable
309
- // such as the var name and subscripts.
310
- let variableReader = new VariableReader(specialSeparationDims, directData)
311
- variableReader.visitModel(tree)
312
- // Add a placeholder variable for the exogenous variable Time.
313
- let v = new Variable(null)
314
- v.modelLHS = 'Time'
315
- v.varName = '_time'
316
- addVariable(v)
317
- }
318
267
  function analyze(parsedModelKind, inputVars, opts) {
319
268
  // Analyze the RHS of each equation in stages after all the variables are read.
320
269
  // Find non-apply-to-all vars that are defined with more than one equation.
@@ -324,23 +273,17 @@ function analyze(parsedModelKind, inputVars, opts) {
324
273
  setRefIds()
325
274
 
326
275
  // If enabled, reduce expressions used in variable definitions.
327
- if (parsedModelKind !== 'vensim-legacy') {
328
- if (opts?.reduceVariables !== false && process.env.SDE_NONPUBLIC_REDUCE_VARIABLES !== '0') {
329
- let reduceMode = opts?.reduceVariables || process.env.SDE_NONPUBLIC_REDUCE_VARIABLES || 'default'
330
- reduceVariables(variables, inputVars || [], reduceMode)
331
- }
276
+ if (opts?.reduceVariables !== false && process.env.SDE_NONPUBLIC_REDUCE_VARIABLES !== '0') {
277
+ let reduceMode = opts?.reduceVariables || process.env.SDE_NONPUBLIC_REDUCE_VARIABLES || 'default'
278
+ reduceVariables(variables, inputVars || [], reduceMode)
332
279
  }
333
280
  if (opts?.stopAfterReduceVariables === true) return
334
281
 
335
282
  // Read the RHS to list the refIds of vars that are referenced and set the var type.
336
- if (parsedModelKind === 'vensim-legacy') {
337
- readEquations()
338
- } else {
339
- variables.forEach(readEquation)
340
- }
283
+ variables.forEach(readEquation)
341
284
  }
342
285
 
343
- function checkSpecVars(spec, extData) {
286
+ function checkSpecVars(spec) {
344
287
  // Look up each var in the spec and issue and throw error if it does not exist.
345
288
 
346
289
  function check(varNames, specType) {
@@ -351,22 +294,9 @@ function checkSpecVars(spec, extData) {
351
294
  // out of the valid range)
352
295
  if (!R.contains('[', varName)) {
353
296
  if (!varWithRefId(varName)) {
354
- // Look for a variable in external data.
355
- if (extData?.has(varName)) {
356
- // console.error(`found ${specType} ${varName} in extData`)
357
- // Copy data from an external file to an equation that does a lookup.
358
- let lookup = R.reduce(
359
- (a, p) => listConcat(a, `(${p[0]}, ${p[1]})`, true),
360
- '',
361
- Array.from(extData.get(varName))
362
- )
363
- let modelEquation = `${decanonicalize(varName)} = WITH LOOKUP(Time, (${lookup}))`
364
- addEquation(modelEquation)
365
- } else {
366
- throw new Error(
367
- `The ${specType} variable ${varName} was declared in spec.json, but no matching variable was found in the model or external data sources`
368
- )
369
- }
297
+ throw new Error(
298
+ `The ${specType} variable ${varName} was declared in spec.json, but no matching variable was found in the model or external data sources`
299
+ )
370
300
  }
371
301
  }
372
302
  }
@@ -511,8 +441,8 @@ function resolveDuplicateDeclarations() {
511
441
  // Least and greatest safe double values in C rounded to convenient consts
512
442
  const MIN_SAFE_DBL = -1e308
513
443
  const MAX_SAFE_DBL = 1e308
514
- let data = dataVars()
515
- for (let constVar of constVars()) {
444
+ let data = varsOfType('data')
445
+ for (let constVar of varsOfType('const')) {
516
446
  if (data.find(d => d.varName === constVar.varName)) {
517
447
  // Change the var type from const to data and add lookup data points.
518
448
  // For a constant, the equivalent lookup has the same value over the entire x axis.
@@ -568,28 +498,6 @@ function setRefIds() {
568
498
  v.refId = refIdForVar(v)
569
499
  }, variables)
570
500
  }
571
- function readEquations() {
572
- // Augment variables with information from their equations.
573
- // This requires a refId for each var so that actual refIds can be resolved for the reference list.
574
- R.forEach(v => {
575
- let equationReader = new EquationReader(v)
576
- equationReader.read()
577
- }, variables)
578
- }
579
- function addEquation(modelEquation) {
580
- // Add an equation in Vensim model format.
581
- let parser = createParser(modelEquation)
582
- let tree = parser.equation()
583
- // Read the var and add it to the Model var table.
584
- let variableReader = new VariableReader()
585
- variableReader.visitEquation(tree)
586
- let v = variableReader.var
587
- // Fill in the refId.
588
- v.refId = refIdForVar(v)
589
- // Finish the variable by parsing the RHS.
590
- let equationReader = new EquationReader(v)
591
- equationReader.read()
592
- }
593
501
  //
594
502
  // Model API
595
503
  //
@@ -618,26 +526,53 @@ function allVars() {
618
526
  }
619
527
  return R.filter(isNotPlaceholderVar, variables)
620
528
  }
529
+ function cachedSortedVars(varType, generate) {
530
+ // Return the cached array of sorted variables for the given type if
531
+ // available, otherwise call the `generate` function to generate the
532
+ // array and cache it in the map.
533
+ let vars = cachedSortedVarsByType.get(varType)
534
+ if (!vars) {
535
+ vars = generate()
536
+ cachedSortedVarsByType.set(varType, vars)
537
+ }
538
+ return vars
539
+ }
621
540
  function constVars() {
622
- return vsort(varsOfType('const'))
541
+ // Return an array of vars of type `const`, sorted by LHS variable name.
542
+ // Note that this caches the result, so should only be called after the
543
+ // model has been fully read and analyzed.
544
+ return cachedSortedVars('const', () => vsort(varsOfType('const')))
623
545
  }
624
546
  function lookupVars() {
625
- return vsort(varsOfType('lookup'))
547
+ // Return an array of vars of type `lookup`, sorted by LHS variable name.
548
+ // Note that this caches the result, so should only be called after the
549
+ // model has been fully read and analyzed.
550
+ return cachedSortedVars('lookup', () => vsort(varsOfType('lookup')))
626
551
  }
627
552
  function dataVars() {
628
- return vsort(varsOfType('data'))
553
+ // Return an array of vars of type `data`, sorted by LHS variable name.
554
+ // Note that this caches the result, so should only be called after the
555
+ // model has been fully read and analyzed.
556
+ return cachedSortedVars('data', () => vsort(varsOfType('data')))
629
557
  }
630
558
  function auxVars() {
631
- // console.error('AUX VARS');
632
- return sortVarsOfType('aux')
559
+ // Return an array of vars of type `aux`, sorted according to the dependency graph.
560
+ // Note that this caches the result, so should only be called after the
561
+ // model has been fully read and analyzed.
562
+ return cachedSortedVars('aux', () => sortVarsOfType('aux'))
633
563
  }
634
564
  function levelVars() {
635
- // console.error('LEVEL VARS');
636
- return sortVarsOfType('level')
565
+ // Return an array of vars of type `level`, sorted according to the dependency graph.
566
+ // Note that this caches the result, so should only be called after the
567
+ // model has been fully read and analyzed.
568
+ return cachedSortedVars('level', () => sortVarsOfType('level'))
637
569
  }
638
570
  function initVars() {
639
- // console.error('INIT VARS');
640
- return sortInitVars()
571
+ // Return an array of all vars that have the `hasInitValue` flag set to true,
572
+ // sorted according to the dependency graph.
573
+ // Note that this caches the result, so should only be called after the
574
+ // model has been fully read and analyzed.
575
+ return cachedSortedVars('init', () => sortInitVars())
641
576
  }
642
577
  function varWithRefId(refId) {
643
578
  const findVarWithRefId = rid => {
@@ -799,11 +734,7 @@ function vensimName(cVarName) {
799
734
  function cName(vensimVarName) {
800
735
  // Convert a Vensim variable name to a C name.
801
736
  // This function requires model analysis to be completed first when the variable has subscripts.
802
- if (process.env.SDE_NONPUBLIC_USE_NEW_PARSE === '0') {
803
- // TODO: For now we use the legacy VarNameReader when the old parser is active; this
804
- // code will be removed once the old parser is removed
805
- return new VarNameReader().read(vensimVarName)
806
- }
737
+
807
738
  // Split the variable name from the subscripts
808
739
  let matches = vensimVarName.match(/([^[]+)(?:\[([^\]]+)\])?/)
809
740
  if (!matches) {
@@ -1153,27 +1084,52 @@ function printDepsGraph(graph, varType) {
1153
1084
 
1154
1085
  function allListedVars() {
1155
1086
  // Put variables into the order that they are evaluated by SDE in the generated model
1156
- let vars = []
1157
- vars.push(...constVars())
1158
- vars.push(...lookupVars())
1159
- vars.push(...dataVars())
1087
+ const listedVars = []
1088
+ const visitedRefIds = new Set()
1089
+ function addUnique(vars) {
1090
+ for (const v of vars) {
1091
+ // Skip variables that have already been visited
1092
+ if (visitedRefIds.has(v.refId)) {
1093
+ continue
1094
+ }
1095
+ visitedRefIds.add(v.refId)
1096
+
1097
+ // Filter out variables that are generated/used internally
1098
+ if (v.includeInOutput === false) {
1099
+ continue
1100
+ }
1101
+
1102
+ // Include the variable
1103
+ listedVars.push(v)
1104
+ }
1105
+ }
1106
+
1107
+ // 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)
1114
+ // So to make the ordering in the listing better match the order of evaluation,
1115
+ // we emit variables in the above order, but filter to avoid having duplicates.
1116
+ addUnique(constVars())
1117
+ addUnique(lookupVars())
1118
+ addUnique(dataVars())
1160
1119
  // The special exogenous `Time` variable may have already been removed by
1161
1120
  // `removeUnusedVariables` if it is not referenced explicitly in the model,
1162
- // so we will only include it in the listing if it is defined here
1121
+ // so we will only include it in the listing if it is defined here. Note
1122
+ // that `_time` is set to `_initial_time` as the first step in the
1123
+ // `initLevels` function, which is why it is included here.
1163
1124
  const timeVar = varWithName('_time')
1164
1125
  if (timeVar) {
1165
- vars.push(timeVar)
1126
+ addUnique([timeVar])
1166
1127
  }
1167
- vars.push(...initVars())
1168
- vars.push(...auxVars())
1169
- // TODO: Also levelVars not covered by initVars?
1128
+ addUnique(initVars())
1129
+ addUnique(auxVars())
1130
+ addUnique(levelVars())
1170
1131
 
1171
- // Filter out data/lookup variables and variables that are generated/used internally
1172
- const isInternal = v => {
1173
- return v.refId.startsWith('__level') || v.refId.startsWith('__aux')
1174
- }
1175
-
1176
- return R.filter(v => !isInternal(v), vars)
1132
+ return listedVars
1177
1133
  }
1178
1134
 
1179
1135
  function filteredListedVars() {
@@ -1185,6 +1141,7 @@ function filteredListedVars() {
1185
1141
  function varIndexInfoMap() {
1186
1142
  // Return a map containing information for each listed variable:
1187
1143
  // varName
1144
+ // varType
1188
1145
  // varIndex
1189
1146
  // subscriptCount
1190
1147
 
@@ -1192,21 +1149,17 @@ function varIndexInfoMap() {
1192
1149
  // generated model
1193
1150
  const sortedVars = filteredListedVars()
1194
1151
 
1195
- // Get the set of unique variable names, and assign a 1-based index
1196
- // to each; this matches the index number used in `storeOutput()`
1197
- // in the generated C code
1152
+ // Get the set of unique variable names, and assign a 1-based index to each.
1153
+ // This matches the index number used in `storeOutput` and `setLookup` in the
1154
+ // generated C/JS code
1198
1155
  const infoMap = new Map()
1199
1156
  let varIndex = 1
1200
1157
  for (const v of sortedVars) {
1201
- if (v.varType === 'data' || v.varType === 'lookup') {
1202
- // Omit the index for data and lookup variables; at this time, the data for these
1203
- // cannot be output like for other types of variables
1204
- continue
1205
- }
1206
1158
  const varName = v.varName
1207
1159
  if (!infoMap.get(varName)) {
1208
1160
  infoMap.set(varName, {
1209
1161
  varName,
1162
+ varType: v.varType,
1210
1163
  varIndex,
1211
1164
  subscriptCount: v.families ? v.families.length : 0
1212
1165
  })
@@ -1221,43 +1174,89 @@ function varIndexInfo() {
1221
1174
  // Return an array, sorted by `varName`, containing information for each
1222
1175
  // listed variable:
1223
1176
  // varName
1177
+ // varType
1224
1178
  // varIndex
1225
1179
  // subscriptCount
1226
- return Array.from(varIndexInfoMap().values())
1180
+ if (cachedVarIndexInfo) {
1181
+ return cachedVarIndexInfo
1182
+ }
1183
+ cachedVarIndexInfo = Array.from(varIndexInfoMap().values())
1184
+ return cachedVarIndexInfo
1227
1185
  }
1228
1186
 
1229
1187
  function jsonList() {
1230
- // Return a stringified JSON object containing variable and subscript information
1231
- // for the model.
1188
+ // Return an object containing variable and subscript information for the model
1189
+ // that will be used to write the JSON model listing files.
1190
+ if (cachedJsonList) {
1191
+ return cachedJsonList
1192
+ }
1232
1193
 
1233
1194
  // Get the set of available subscripts
1234
1195
  const allDims = [...allDimensions()]
1235
- const sortedDims = allDims.sort((a, b) => a.name.localeCompare(b.name))
1196
+ const sortedFullDims = allDims.sort((a, b) => a.name.localeCompare(b.name))
1236
1197
 
1237
1198
  // Extract a subset of the available info for each variable and put them in eval order
1238
- const sortedVars = filteredListedVars()
1199
+ const sortedFullVars = filteredListedVars()
1239
1200
 
1240
1201
  // Assign a 1-based index for each variable that has data that can be accessed.
1241
- // This matches the index number used in `storeOutput()` in the generated C code.
1202
+ // This matches the index number used in `storeOutput` and `setLookup` in the
1203
+ // generated C/JS code
1242
1204
  const infoMap = varIndexInfoMap()
1243
- for (const v of sortedVars) {
1205
+ for (const v of sortedFullVars) {
1244
1206
  const varInfo = infoMap.get(v.varName)
1245
1207
  if (varInfo) {
1246
1208
  v.varIndex = varInfo.varIndex
1247
1209
  }
1248
1210
  }
1249
1211
 
1250
- // Convert to JSON
1251
- const obj = {
1252
- dimensions: sortedDims,
1253
- variables: sortedVars
1212
+ // Derive minimal versions of the full arrays; these only contain the minimal
1213
+ // subset of fields that are needed by the `ModelListing` class from the
1214
+ // runtime package. The property names in the minimal objects are slightly
1215
+ // different than the full ones to better match the latest naming used in the
1216
+ // compile and runtime packages.
1217
+ const sortedMinimalDims = sortedFullDims.map(d => {
1218
+ return {
1219
+ id: d.name,
1220
+ subIds: d.value
1221
+ }
1222
+ })
1223
+
1224
+ // Note that `sortedFullVars` may contain duplicates in the case of separated
1225
+ // variables, but for the minimal listing we only want to have one entry per
1226
+ // index (i.e., one entry for each base variable ID), so we filter out the
1227
+ // duplicates here.
1228
+ const baseIds = new Set()
1229
+ const sortedMinimalVars = []
1230
+ for (const v of sortedFullVars) {
1231
+ const baseId = v.varName
1232
+ if (!baseIds.has(baseId)) {
1233
+ baseIds.add(baseId)
1234
+
1235
+ const varInfo = {}
1236
+ varInfo.id = baseId
1237
+ if (v.families) {
1238
+ varInfo.dimIds = v.families
1239
+ }
1240
+ varInfo.index = v.varIndex
1241
+ sortedMinimalVars.push(varInfo)
1242
+ }
1243
+ }
1244
+
1245
+ cachedJsonList = {
1246
+ full: {
1247
+ dimensions: sortedFullDims,
1248
+ variables: sortedFullVars
1249
+ },
1250
+ minimal: {
1251
+ dimensions: sortedMinimalDims,
1252
+ variables: sortedMinimalVars
1253
+ }
1254
1254
  }
1255
- return JSON.stringify(obj, null, 2)
1255
+ return cachedJsonList
1256
1256
  }
1257
1257
 
1258
1258
  export default {
1259
1259
  addConstantExpr,
1260
- addEquation,
1261
1260
  addNonAtoAVar,
1262
1261
  addVariable,
1263
1262
  allVars,