@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.
- package/package.json +1 -3
- package/src/generate/expand-var-names.js +0 -10
- package/src/generate/{code-gen.js → gen-code-c.js} +188 -89
- package/src/generate/gen-code-js.js +616 -0
- package/src/generate/gen-code.js +36 -0
- package/src/generate/gen-equation.js +16 -6
- package/src/generate/gen-expr.js +205 -48
- package/src/generate/gen-lookup-from-direct.js +14 -6
- package/src/generate/gen-lookup-from-external.js +20 -5
- package/src/generate/gen-lookup-from-points.js +28 -6
- package/src/index.js +38 -3
- package/src/model/model.js +169 -170
- package/src/model/read-equation-fn-game.js +50 -0
- package/src/model/read-equations.js +9 -10
- package/src/model/read-variables.js +2 -2
- package/src/model/variable.js +6 -21
- package/src/parse-and-generate.js +22 -19
- package/src/preprocess/preprocessor.js +2 -2
- package/src/generate/equation-gen.js +0 -1268
- package/src/generate/model-lhs-reader.js +0 -88
- package/src/model/equation-reader.js +0 -723
- package/src/model/expr-reader.js +0 -202
- package/src/model/subscript-range-reader.js +0 -143
- package/src/model/var-name-reader.js +0 -40
- package/src/model/variable-reader.js +0 -172
- package/src/parse/model-reader.js +0 -141
- package/src/parse/parser.js +0 -36
|
@@ -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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
7
|
-
export {
|
|
8
|
-
|
|
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
|
+
}
|
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 { canonicalName, decanonicalize, isIterable, listConcat
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
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 (
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
337
|
-
readEquations()
|
|
338
|
-
} else {
|
|
339
|
-
variables.forEach(readEquation)
|
|
340
|
-
}
|
|
283
|
+
variables.forEach(readEquation)
|
|
341
284
|
}
|
|
342
285
|
|
|
343
|
-
function checkSpecVars(spec
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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 =
|
|
515
|
-
for (let constVar of
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
632
|
-
|
|
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
|
-
//
|
|
636
|
-
|
|
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
|
-
//
|
|
640
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
vars
|
|
1159
|
-
|
|
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
|
-
|
|
1126
|
+
addUnique([timeVar])
|
|
1166
1127
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1128
|
+
addUnique(initVars())
|
|
1129
|
+
addUnique(auxVars())
|
|
1130
|
+
addUnique(levelVars())
|
|
1170
1131
|
|
|
1171
|
-
|
|
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
|
-
//
|
|
1197
|
-
//
|
|
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
|
-
|
|
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
|
|
1231
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
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
|
|
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,
|