@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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { canonicalName } from '../_shared/helpers.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a lookup variable that can be used to provide inputs to a the `GAME`
|
|
5
|
+
* function at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @param {*} v
|
|
8
|
+
* @param {*} callExpr
|
|
9
|
+
* @param {*} context
|
|
10
|
+
*/
|
|
11
|
+
export function generateGameVariables(v, callExpr, context) {
|
|
12
|
+
// If the LHS includes subscripts, use those same subscripts when generating
|
|
13
|
+
// the new lookup variable
|
|
14
|
+
let subs
|
|
15
|
+
if (context.eqnLhs.varDef.subscriptRefs) {
|
|
16
|
+
const subNames = context.eqnLhs.varDef.subscriptRefs.map(subRef => subRef.subName)
|
|
17
|
+
subs = `[${subNames.join(',')}]`
|
|
18
|
+
} else {
|
|
19
|
+
subs = ''
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Synthesize a lookup variable name that is the same as the LHS variable
|
|
23
|
+
// name with ' game inputs' appended to it
|
|
24
|
+
const gameLookupVarName = context.eqnLhs.varDef.varName + ' game inputs'
|
|
25
|
+
|
|
26
|
+
// Add a reference to the synthesized game inputs lookup
|
|
27
|
+
const gameLookupVarId = canonicalName(gameLookupVarName)
|
|
28
|
+
v.gameLookupVarName = gameLookupVarId
|
|
29
|
+
if (v.referencedLookupVarNames) {
|
|
30
|
+
v.referencedLookupVarNames.push(gameLookupVarId)
|
|
31
|
+
} else {
|
|
32
|
+
v.referencedLookupVarNames = [gameLookupVarId]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Define a variable for the synthesized game inputs lookup
|
|
36
|
+
const gameLookupVars = context.defineVariable(`${gameLookupVarName}${subs} ~~|`)
|
|
37
|
+
|
|
38
|
+
// Normally `defineVariable` sets `includeInOutput` to false for generated
|
|
39
|
+
// variables, but we want the generated lookup variable to appear in the
|
|
40
|
+
// model listing so that the user can reference it, so set `includeInOutput`
|
|
41
|
+
// to true. Also change the `varType` to 'lookup' instead of 'data'. We
|
|
42
|
+
// will declare a `Lookup` variable in the generated code, but unlike a
|
|
43
|
+
// normal lookup, we won't initialize it with data by default (it can only
|
|
44
|
+
// be updated at runtime).
|
|
45
|
+
gameLookupVars.forEach(v => {
|
|
46
|
+
v.includeInOutput = true
|
|
47
|
+
v.varType = 'lookup'
|
|
48
|
+
v.varSubtype = 'gameInputs'
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
|
|
17
17
|
import Model from './model.js'
|
|
18
18
|
import { generateDelayVariables } from './read-equation-fn-delay.js'
|
|
19
|
+
import { generateGameVariables } from './read-equation-fn-game.js'
|
|
19
20
|
import { generateNpvVariables } from './read-equation-fn-npv.js'
|
|
20
21
|
import { generateSmoothVariables } from './read-equation-fn-smooth.js'
|
|
21
22
|
import { generateTrendVariables } from './read-equation-fn-trend.js'
|
|
@@ -128,6 +129,8 @@ class Context {
|
|
|
128
129
|
// Inhibit output for generated variables
|
|
129
130
|
v.includeInOutput = false
|
|
130
131
|
})
|
|
132
|
+
|
|
133
|
+
return vars
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
/**
|
|
@@ -383,16 +386,6 @@ function visitFunctionCall(v, callExpr, context) {
|
|
|
383
386
|
validateCallArgs(callExpr, 1)
|
|
384
387
|
break
|
|
385
388
|
|
|
386
|
-
// TODO: We do not currently have full support for the GAME function, so report a warning for now
|
|
387
|
-
case '_GAME':
|
|
388
|
-
if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS !== '0') {
|
|
389
|
-
console.warn(
|
|
390
|
-
`WARNING: The GAME function (used in the definition of '${v.modelLHS}') is currently implemented as a no-op (it returns the input value).`
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
validateCallArgs(callExpr, 1)
|
|
394
|
-
break
|
|
395
|
-
|
|
396
389
|
//
|
|
397
390
|
//
|
|
398
391
|
// 2-argument functions...
|
|
@@ -491,6 +484,12 @@ function visitFunctionCall(v, callExpr, context) {
|
|
|
491
484
|
argModes[2] = 'init'
|
|
492
485
|
break
|
|
493
486
|
|
|
487
|
+
case '_GAME':
|
|
488
|
+
validateCallDepth(callExpr, context)
|
|
489
|
+
validateCallArgs(callExpr, 1)
|
|
490
|
+
generateGameVariables(v, callExpr, context)
|
|
491
|
+
break
|
|
492
|
+
|
|
494
493
|
case '_GET_DIRECT_CONSTANTS': {
|
|
495
494
|
validateCallDepth(callExpr, context)
|
|
496
495
|
validateCallArgs(callExpr, 3)
|
|
@@ -49,7 +49,7 @@ export function readVariables(parsedModel, specialSeparationDims) {
|
|
|
49
49
|
*/
|
|
50
50
|
function variablesForEquation(eqn, specialSeparationDims) {
|
|
51
51
|
// Start a new variable defined by this equation
|
|
52
|
-
const variable = new Variable(
|
|
52
|
+
const variable = new Variable()
|
|
53
53
|
|
|
54
54
|
// Fill in the LHS details
|
|
55
55
|
const lhs = eqn.lhs.varDef
|
|
@@ -137,7 +137,7 @@ function variablesForEquation(eqn, specialSeparationDims) {
|
|
|
137
137
|
// Generate variables expanded over subscripts to the model
|
|
138
138
|
const variables = []
|
|
139
139
|
for (const expansion of expansions) {
|
|
140
|
-
const v = new Variable(
|
|
140
|
+
const v = new Variable()
|
|
141
141
|
v.varName = baseVarId
|
|
142
142
|
v.modelLHS = lhsText
|
|
143
143
|
v.modelFormula = rhsText
|
package/src/model/variable.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
export default class Variable {
|
|
2
|
-
constructor(
|
|
3
|
-
// The equation rule context allows us to generate code by visiting the parse tree.
|
|
4
|
-
this.eqnCtx = eqnCtx
|
|
2
|
+
constructor() {
|
|
5
3
|
// Save both sides of the equation text in the model for documentation purposes.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.modelFormula = this.formula(eqnCtx)
|
|
9
|
-
} else {
|
|
10
|
-
this.modelLHS = ''
|
|
11
|
-
this.modelFormula = ''
|
|
12
|
-
}
|
|
4
|
+
this.modelLHS = ''
|
|
5
|
+
this.modelFormula = ''
|
|
13
6
|
// An equation defines a variable with a var name, saved in canonical form here.
|
|
14
7
|
this.varName = ''
|
|
15
8
|
// Subscripts are canonical dimension or index names on the LHS in normal order.
|
|
@@ -48,6 +41,8 @@ export default class Variable {
|
|
|
48
41
|
// DELAY3* calls are expanded into new level vars and substituted during code generation.
|
|
49
42
|
this.delayVarRefId = ''
|
|
50
43
|
this.delayTimeVarName = ''
|
|
44
|
+
// GAME calls generate a Lookup support var.
|
|
45
|
+
this.gameLookupVarName = ''
|
|
51
46
|
// DELAY FIXED calls generate a FixedDelay support var.
|
|
52
47
|
this.fixedDelayVarName = ''
|
|
53
48
|
// DEPRECIATE STRAIGHTLINE calls generate a Depreciation support var.
|
|
@@ -57,7 +52,6 @@ export default class Variable {
|
|
|
57
52
|
}
|
|
58
53
|
copy() {
|
|
59
54
|
let c = new Variable()
|
|
60
|
-
c.eqnCtx = this.eqnCtx
|
|
61
55
|
c.modelLHS = this.modelLHS
|
|
62
56
|
c.modelFormula = this.modelFormula
|
|
63
57
|
c.varName = this.varName
|
|
@@ -75,19 +69,10 @@ export default class Variable {
|
|
|
75
69
|
c.trendVarName = this.trendVarName
|
|
76
70
|
c.delayVarRefId = this.delayVarRefId
|
|
77
71
|
c.delayTimeVarName = this.delayTimeVarName
|
|
72
|
+
c.gameLookupVarName = this.gameLookupVarName
|
|
78
73
|
c.includeInOutput = this.includeInOutput
|
|
79
74
|
return c
|
|
80
75
|
}
|
|
81
|
-
formula(eqnCtx) {
|
|
82
|
-
if (eqnCtx) {
|
|
83
|
-
if (eqnCtx.expr()) {
|
|
84
|
-
return eqnCtx.expr().getText()
|
|
85
|
-
} else if (eqnCtx.constList()) {
|
|
86
|
-
return eqnCtx.constList().getText()
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return ''
|
|
90
|
-
}
|
|
91
76
|
hasSubscripts() {
|
|
92
77
|
return this.subscripts.length > 0
|
|
93
78
|
}
|
|
@@ -8,10 +8,9 @@ import { parseVensimModel } from '@sdeverywhere/parse'
|
|
|
8
8
|
import { readXlsx } from './_shared/helpers.js'
|
|
9
9
|
import { readDat } from './_shared/read-dat.js'
|
|
10
10
|
import { printSubscripts, yamlSubsList } from './_shared/subscript.js'
|
|
11
|
-
import { parseModel as legacyParseVensimModel } from './parse/parser.js'
|
|
12
11
|
import Model from './model/model.js'
|
|
13
12
|
import { getDirectSubscripts } from './model/read-subscripts.js'
|
|
14
|
-
import { generateCode } from './generate/code
|
|
13
|
+
import { generateCode } from './generate/gen-code.js'
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Parse a Vensim model and generate C code.
|
|
@@ -19,17 +18,18 @@ import { generateCode } from './generate/code-gen.js'
|
|
|
19
18
|
* This is the primary entrypoint for the `sde generate` command.
|
|
20
19
|
*
|
|
21
20
|
* - If `operations` has 'generateC', the generated C code will be written to `buildDir`.
|
|
21
|
+
* - If `operations` has 'generateJS', the generated JS code will be written to `buildDir`.
|
|
22
22
|
* - If `operations` has 'printVarList', variables and subscripts will be written to
|
|
23
23
|
* txt, yaml, and json files under `buildDir`.
|
|
24
|
-
* - If `
|
|
25
|
-
* - If `
|
|
24
|
+
* - If `operations` has 'printRefIdTest', reference identifiers will be printed to the console.
|
|
25
|
+
* - If `operations` has 'convertNames', no output will be generated, but the results of model
|
|
26
26
|
* analysis will be available.
|
|
27
27
|
*
|
|
28
28
|
* @param input The preprocessed Vensim model text.
|
|
29
29
|
* @param spec The model spec (from the JSON file).
|
|
30
|
-
* @param operations The set of operations to perform; can include 'generateC', '
|
|
31
|
-
* 'printRefIdTest', 'convertNames'. If the array is empty, the model will be
|
|
32
|
-
* operation will be performed.
|
|
30
|
+
* @param operations The set of operations to perform; can include 'generateC', 'generateJS',
|
|
31
|
+
* 'printVarList', 'printRefIdTest', 'convertNames'. If the array is empty, the model will be
|
|
32
|
+
* read but no operation will be performed.
|
|
33
33
|
* @param modelDirname The absolute path to the directory containing the mdl file.
|
|
34
34
|
* The dat and xlsx files referenced by the spec will be relative to this directory.
|
|
35
35
|
* @param modelName The model name (without the mdl extension).
|
|
@@ -66,7 +66,7 @@ export async function parseAndGenerate(input, spec, operations, modelDirname, mo
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// Parse the model and generate code
|
|
69
|
+
// Parse the model and generate code
|
|
70
70
|
let parsedModel = parseModel(input, modelDirname)
|
|
71
71
|
let code = generateCode(parsedModel, { spec, operations, extData, directData, modelDirname })
|
|
72
72
|
|
|
@@ -80,6 +80,11 @@ export async function parseAndGenerate(input, spec, operations, modelDirname, mo
|
|
|
80
80
|
writeOutput(`${modelName}.c`, code)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
if (operations.includes('generateJS')) {
|
|
84
|
+
// Write the generated JS to a file
|
|
85
|
+
writeOutput(`${modelName}.js`, code)
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
if (operations.includes('printVarList')) {
|
|
84
89
|
// Write variables to a text file.
|
|
85
90
|
writeOutput(`${modelName}_vars.txt`, Model.printVarList())
|
|
@@ -90,7 +95,11 @@ export async function parseAndGenerate(input, spec, operations, modelDirname, mo
|
|
|
90
95
|
// Write subscripts to a YAML file.
|
|
91
96
|
writeOutput(`${modelName}_subs.yaml`, yamlSubsList())
|
|
92
97
|
// Write variables and subscripts to a JSON file.
|
|
93
|
-
|
|
98
|
+
const jsonList = Model.jsonList()
|
|
99
|
+
writeOutput(`${modelName}.json`, JSON.stringify(jsonList.full, null, 2))
|
|
100
|
+
// Write minimal variable index and dimension specs to a JSON file used
|
|
101
|
+
// by the runtime package to initialize a `ModelListing` instance).
|
|
102
|
+
writeOutput(`${modelName}_min.json`, JSON.stringify(jsonList.minimal, null, 2))
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
return code
|
|
@@ -127,18 +136,11 @@ export function printNames(namesPathname, operation) {
|
|
|
127
136
|
* @param {string} input The string containing the model text.
|
|
128
137
|
* @param {string} modelDir The absolute path to the directory containing the mdl file.
|
|
129
138
|
* The dat, xlsx, and csv files referenced by the model will be relative to this directory.
|
|
130
|
-
* @param {
|
|
139
|
+
* @param {Object} options The options that control parsing.
|
|
140
|
+
* @param {boolean} options.sort Whether to sort definitions alphabetically in the preprocess step.
|
|
131
141
|
* @return {*} A parsed tree representation of the model.
|
|
132
142
|
*/
|
|
133
|
-
export function parseModel(input, modelDir,
|
|
134
|
-
if (process.env.SDE_NONPUBLIC_USE_NEW_PARSE === '0') {
|
|
135
|
-
// Use the legacy parser
|
|
136
|
-
return {
|
|
137
|
-
kind: 'vensim-legacy',
|
|
138
|
-
parseTree: legacyParseVensimModel(input)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
143
|
+
export function parseModel(input, modelDir, options) {
|
|
142
144
|
// Prepare the parse context that provides access to external data files
|
|
143
145
|
let parseContext /*: VensimParseContext*/
|
|
144
146
|
if (modelDir) {
|
|
@@ -160,6 +162,7 @@ export function parseModel(input, modelDir, sort = false) {
|
|
|
160
162
|
// TODO: We currently sort the preprocessed definitions alphabetically for
|
|
161
163
|
// compatibility with the legacy preprocessor. Once we drop the legacy code
|
|
162
164
|
// we could remove this step and update the tests to use the original order.
|
|
165
|
+
const sort = options?.sort === true
|
|
163
166
|
const root = parseVensimModel(input, parseContext, sort)
|
|
164
167
|
|
|
165
168
|
return {
|
|
@@ -3,14 +3,14 @@ import B from 'bufx'
|
|
|
3
3
|
import * as R from 'ramda'
|
|
4
4
|
import { splitEquations, replaceDelimitedStrings } from '../_shared/helpers.js'
|
|
5
5
|
|
|
6
|
-
export let preprocessModel = (mdlFilename, spec, profile = '
|
|
6
|
+
export let preprocessModel = (mdlFilename, spec, profile = 'runnable', writeFiles = false, outDecls = []) => {
|
|
7
7
|
const MACROS_FILENAME = 'macros.txt'
|
|
8
8
|
const REMOVALS_FILENAME = 'removals.txt'
|
|
9
9
|
const INSERTIONS_FILENAME = 'mdl-edits.txt'
|
|
10
10
|
const ENCODING = '{UTF-8}'
|
|
11
11
|
let profiles = {
|
|
12
12
|
// simplified but still runnable model
|
|
13
|
-
|
|
13
|
+
runnable: {
|
|
14
14
|
emitEncoding: true,
|
|
15
15
|
emitCommentMarkers: true,
|
|
16
16
|
joinFormulaLines: false
|