@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.
@@ -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(null)
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(null)
140
+ const v = new Variable()
141
141
  v.varName = baseVarId
142
142
  v.modelLHS = lhsText
143
143
  v.modelFormula = rhsText
@@ -1,15 +1,8 @@
1
1
  export default class Variable {
2
- constructor(eqnCtx) {
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
- if (eqnCtx) {
7
- this.modelLHS = eqnCtx.lhs().getText()
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-gen.js'
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 `operation` has 'printRefIdTest', reference identifiers will be printed to the console.
25
- * - If `operation` has 'convertNames', no output will be generated, but the results of model
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', 'printVarList',
31
- * 'printRefIdTest', 'convertNames'. If the array is empty, the model will be read but no
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
- writeOutput(`${modelName}.json`, Model.jsonList())
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 {boolean} sort Whether to sort definitions alphabetically in the preprocess step.
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, sort = false) {
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 = 'genc', writeFiles = false, outDecls = []) => {
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
- genc: {
13
+ runnable: {
14
14
  emitEncoding: true,
15
15
  emitCommentMarkers: true,
16
16
  joinFormulaLines: false