@sdeverywhere/compile 0.7.16 → 0.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "dependencies": {
8
8
  "@sdeverywhere/parse": "^0.1.0",
9
- "antlr4": "4.12.0",
10
- "antlr4-vensim": "0.6.2",
11
9
  "bufx": "^1.0.5",
12
10
  "byline": "^5.0.0",
13
11
  "csv-parse": "^5.3.3",
@@ -5,8 +5,6 @@ import { sub, isDimension } from '../_shared/subscript.js'
5
5
 
6
6
  import Model from '../model/model.js'
7
7
 
8
- import ModelLHSReader from './model-lhs-reader.js'
9
-
10
8
  /**
11
9
  * Return an array of names for all variable in the model, sorted alphabetically and expanded to
12
10
  * include the full set of subscripted variants for variables that include subscripts.
@@ -43,14 +41,6 @@ export function expandVarNames(canonical) {
43
41
  * @returns {string[]} An array of expanded names for the given variable.
44
42
  */
45
43
  function namesForVar(v) {
46
- if (process.env.SDE_NONPUBLIC_USE_NEW_PARSE === '0') {
47
- // TODO: When the old parsing code is active, use the old ModelLHSReader. This code path
48
- // will be removed when the old parsing code is removed.
49
- let modelLHSReader = new ModelLHSReader()
50
- modelLHSReader.read(v.modelLHS)
51
- return modelLHSReader.names()
52
- }
53
-
54
44
  if (v.parsedEqn === undefined) {
55
45
  // XXX: The special `Time` variable does not have a `parsedEqn`, so use the raw LHS
56
46
  return [v.modelLHS]
@@ -1,14 +1,13 @@
1
1
  import * as R from 'ramda'
2
2
 
3
- import { asort, lines, strlist, mapIndexed } from '../_shared/helpers.js'
3
+ import { asort, canonicalVensimName, lines, strlist, mapIndexed } from '../_shared/helpers.js'
4
4
  import { sub, allDimensions, allMappings, subscriptFamilies } from '../_shared/subscript.js'
5
5
  import Model from '../model/model.js'
6
6
 
7
7
  import { generateEquation } from './gen-equation.js'
8
- import EquationGen from './equation-gen.js'
9
8
  import { expandVarNames } from './expand-var-names.js'
10
9
 
11
- export function generateCode(parsedModel, opts) {
10
+ export function generateC(parsedModel, opts) {
12
11
  return codeGenerator(parsedModel, opts).generate()
13
12
  }
14
13
 
@@ -17,21 +16,10 @@ let codeGenerator = (parsedModel, opts) => {
17
16
  // Set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated.
18
17
  let mode = ''
19
18
  // Set to true to output all variables when there is no model run spec.
20
- let outputAllVars
21
- if (spec.outputVars && spec.outputVars.length > 0) {
22
- outputAllVars = false
23
- } else if (spec.outputVarNames && spec.outputVarNames.length > 0) {
24
- outputAllVars = false
25
- } else {
26
- outputAllVars = true
27
- }
19
+ let outputAllVars = spec.outputVarNames === undefined || spec.outputVarNames.length === 0
28
20
  // Function to generate a section of the code
29
21
  let generateSection = R.map(v => {
30
- if (parsedModel.kind === 'vensim-legacy') {
31
- return new EquationGen(v, extData, directData, mode, modelDirname).generate()
32
- } else {
33
- return generateEquation(v, mode, extData, directData, modelDirname)
34
- }
22
+ return generateEquation(v, mode, extData, directData, modelDirname, 'c')
35
23
  })
36
24
  let section = R.pipe(generateSection, R.flatten, lines)
37
25
  function generate() {
@@ -93,51 +81,54 @@ ${section(Model.dataVars())}
93
81
  //
94
82
  function emitInitLookupsCode() {
95
83
  mode = 'init-lookups'
96
- let code = `// Internal state
84
+ let code = `\
85
+ // Internal state
97
86
  bool lookups_initialized = false;
98
87
  bool data_initialized = false;
88
+
99
89
  `
100
90
  code += chunkedFunctions(
101
91
  'initLookups',
102
92
  Model.lookupVars(),
103
- ` // Initialize lookups.
104
- if (!lookups_initialized) {
105
- `,
106
- ` lookups_initialized = true;
107
- }
108
- `
93
+ `\
94
+ // Initialize lookups.
95
+ if (lookups_initialized) {
96
+ return;
97
+ }`,
98
+ ' lookups_initialized = true;'
109
99
  )
110
100
  code += chunkedFunctions(
111
101
  'initData',
112
102
  Model.dataVars(),
113
- ` // Initialize data.
114
- if (!data_initialized) {
115
- `,
116
- ` data_initialized = true;
117
- }
118
- `
103
+ `\
104
+ // Initialize data.
105
+ if (data_initialized) {
106
+ return;
107
+ }`,
108
+ ' data_initialized = true;'
119
109
  )
120
110
  return code
121
111
  }
122
112
 
123
113
  function emitInitConstantsCode() {
124
114
  mode = 'init-constants'
125
- return `
126
- ${chunkedFunctions('initConstants', Model.constVars(), ' // Initialize constants.', ' initLookups();\n initData();')}
127
- `
115
+ return chunkedFunctions(
116
+ 'initConstants',
117
+ Model.constVars(),
118
+ ' // Initialize constants.',
119
+ ' initLookups();\n initData();'
120
+ )
128
121
  }
129
122
 
130
123
  function emitInitLevelsCode() {
131
124
  mode = 'init-levels'
132
- return `
133
- ${chunkedFunctions(
134
- 'initLevels',
135
- Model.initVars(),
136
- `
125
+ return chunkedFunctions(
126
+ 'initLevels',
127
+ Model.initVars(),
128
+ `\
137
129
  // Initialize variables with initialization values, such as levels, and the variables they depend on.
138
130
  _time = _initial_time;`
139
- )}
140
- `
131
+ )
141
132
  }
142
133
 
143
134
  //
@@ -146,40 +137,96 @@ ${chunkedFunctions(
146
137
  function emitEvalCode() {
147
138
  mode = 'eval'
148
139
 
149
- return `
150
- ${chunkedFunctions('evalAux', Model.auxVars(), ' // Evaluate auxiliaries in order from the bottom up.')}
151
-
152
- ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}
153
- `
140
+ return `\
141
+ ${chunkedFunctions('evalAux', Model.auxVars(), ' // Evaluate auxiliaries in order from the bottom up.')}\
142
+ ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}`
154
143
  }
155
144
 
156
145
  //
157
146
  // Input/output section
158
147
  //
159
148
  function emitIOCode() {
160
- let headerVars = outputAllVars ? expandedVarNames(true) : spec.outputVars
161
- let outputVars = outputAllVars ? expandedVarNames() : spec.outputVars
149
+ // Configure the body of the `setLookup` function depending on the value
150
+ // of the `customLookups` property in the spec file
151
+ // TODO: The fprintf calls should be replaced with a mechanism that throws
152
+ // an error (we could add a wrapper function at the JS level)
153
+ let setLookupBody
154
+ if (spec.customLookups === true || Array.isArray(spec.customLookups)) {
155
+ setLookupBody = `\
156
+ switch (varIndex) {
157
+ ${setLookupImpl(Model.varIndexInfo(), spec.customLookups)}
158
+ default:
159
+ fprintf(stderr, "No lookup found for var index %zu in setLookup\\n", varIndex);
160
+ break;
161
+ }`
162
+ } else {
163
+ let msg = 'The setLookup function was not enabled for the generated model. '
164
+ msg += 'Set the customLookups property in the spec/config file to allow for overriding lookups at runtime.'
165
+ setLookupBody = `\
166
+ fprintf(stderr, "${msg}\\n");`
167
+ }
168
+
169
+ // Configure the output variables that appear in the generated `getHeader`
170
+ // and `storeOutputData` functions
171
+ let headerVarNames = outputAllVars ? expandedVarNames(true) : spec.outputVarNames
172
+ let outputVarIds = outputAllVars ? expandedVarNames() : spec.outputVars
173
+
174
+ // Configure the body of the `storeOutput` function depending on the value
175
+ // of the `customOutputs` property in the spec file
176
+ let storeOutputBody
177
+ if (spec.customOutputs === true || Array.isArray(spec.customOutputs)) {
178
+ storeOutputBody = `\
179
+ switch (varIndex) {
180
+ ${customOutputSection(Model.varIndexInfo(), spec.customOutputs)}
181
+ default:
182
+ fprintf(stderr, "No variable found for var index %zu in storeOutput\\n", varIndex);
183
+ break;
184
+ }`
185
+ } else {
186
+ let msg = 'The storeOutput function was not enabled for the generated model. '
187
+ msg +=
188
+ 'Set the customOutputs property in the spec/config file to allow for capturing arbitrary variables at runtime.'
189
+ storeOutputBody = `\
190
+ fprintf(stderr, "${msg}\\n");`
191
+ }
192
+
162
193
  mode = 'io'
163
- return `void setInputs(const char* inputData) {${inputsFromStringImpl()}}
194
+ return `\
195
+ void setInputs(const char* inputData) {
196
+ ${inputsFromStringImpl()}
197
+ }
198
+
199
+ void setInputsFromBuffer(double* inputData) {
200
+ ${inputsFromBufferImpl()}
201
+ }
202
+
203
+ void replaceLookup(Lookup** lookup, double* points, size_t numPoints) {
204
+ if (lookup == NULL) {
205
+ return;
206
+ }
207
+ if (*lookup != NULL) {
208
+ __delete_lookup(*lookup);
209
+ *lookup = NULL;
210
+ }
211
+ if (points != NULL) {
212
+ *lookup = __new_lookup(numPoints, /*copy=*/true, points);
213
+ }
214
+ }
164
215
 
165
- void setInputsFromBuffer(double* inputData) {${inputsFromBufferImpl()}}
216
+ void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints) {
217
+ ${setLookupBody}
218
+ }
166
219
 
167
220
  const char* getHeader() {
168
- return "${R.map(varName => headerTitle(varName), headerVars).join('\\t')}";
221
+ return "${R.map(varName => varName.replace(/"/g, '\\"'), headerVarNames).join('\\t')}";
169
222
  }
170
223
 
171
224
  void storeOutputData() {
172
- ${specOutputSection(outputVars)}
225
+ ${specOutputSection(outputVarIds)}
173
226
  }
174
227
 
175
- void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
176
- #if SDE_USE_OUTPUT_INDICES
177
- switch (varIndex) {
178
- ${fullOutputSection(Model.varIndexInfo())}
179
- default:
180
- break;
181
- }
182
- #endif
228
+ void storeOutput(size_t varIndex, size_t* subIndices) {
229
+ ${storeOutputBody}
183
230
  }
184
231
  `
185
232
  }
@@ -190,9 +237,9 @@ ${fullOutputSection(Model.varIndexInfo())}
190
237
  function chunkedFunctions(name, vars, preStep, postStep) {
191
238
  // Emit one function for each chunk
192
239
  let func = (chunk, idx) => {
193
- return `
240
+ return `\
194
241
  void ${name}${idx}() {
195
- ${section(chunk)}
242
+ ${section(chunk)}
196
243
  }
197
244
  `
198
245
  }
@@ -220,22 +267,25 @@ void ${name}${idx}() {
220
267
  chunks = [vars]
221
268
  }
222
269
 
223
- if (!preStep) {
224
- preStep = ''
270
+ let funcsPart = funcs(chunks)
271
+ let callsPart = funcCalls(chunks)
272
+
273
+ let out = ''
274
+ if (funcsPart.length > 0) {
275
+ out += funcsPart + '\n'
225
276
  }
226
- if (!postStep) {
227
- postStep = ''
277
+ out += `void ${name}() {\n`
278
+ if (preStep) {
279
+ out += preStep + '\n'
228
280
  }
229
-
230
- return `
231
- ${funcs(chunks)}
232
-
233
- void ${name}() {
234
- ${preStep}
235
- ${funcCalls(chunks)}
236
- ${postStep}
237
- }
238
- `
281
+ if (callsPart.length > 0) {
282
+ out += callsPart + '\n'
283
+ }
284
+ if (postStep) {
285
+ out += postStep + '\n'
286
+ }
287
+ out += '}\n\n'
288
+ return out
239
289
  }
240
290
 
241
291
  //
@@ -283,9 +333,6 @@ ${postStep}
283
333
  } else {
284
334
  decls = `const int numOutputs = ${spec.outputVars.length};`
285
335
  }
286
- decls += `\n#define SDE_USE_OUTPUT_INDICES 0`
287
- decls += `\n#define SDE_MAX_OUTPUT_INDICES 1000`
288
- decls += `\nconst int maxOutputIndices = SDE_USE_OUTPUT_INDICES ? SDE_MAX_OUTPUT_INDICES : 0;`
289
336
  return decls
290
337
  }
291
338
  function arrayDimensionsSection() {
@@ -320,31 +367,44 @@ ${postStep}
320
367
  // Input/output section helpers
321
368
  //
322
369
  function specOutputSection(varNames) {
323
- // Emit output calls using varNames in C format.
370
+ // Emit `outputVar` calls for all variables listed in the `outputVarNames`
371
+ // array in the spec file using varNames in C format.
324
372
  let code = R.map(varName => ` outputVar(${varName});`)
325
373
  let section = R.pipe(code, lines)
326
374
  return section(varNames)
327
375
  }
328
- function fullOutputSection(varIndexInfo) {
329
- // Emit output calls for all variables.
376
+ function customOutputSection(varIndexInfo, customOutputs) {
377
+ // Emit `outputVar` calls for all variables that can be accessed as an output.
378
+ // This excludes data and lookup variables; at this time, the data for these
379
+ // cannot be output like for other types of variables.
380
+ let includeCase
381
+ if (Array.isArray(customOutputs)) {
382
+ // Only include a case statement if the variable was explicitly included
383
+ // in the `customOutputs` array in the spec file
384
+ const customOutputVarNames = customOutputs.map(varName => {
385
+ // The developer might specify a variable name that includes subscripts,
386
+ // but we will ignore the subscript part and only match on the base name
387
+ return canonicalVensimName(varName.split('[')[0])
388
+ })
389
+ includeCase = varName => customOutputVarNames.includes(varName)
390
+ } else {
391
+ // Include a case statement for all accessible variables
392
+ includeCase = () => true
393
+ }
394
+ const outputVars = R.filter(info => {
395
+ return info.varType !== 'lookup' && info.varType !== 'data' && includeCase(info.varName)
396
+ })
330
397
  const code = R.map(info => {
331
398
  let varAccess = info.varName
332
- if (info.subscriptCount > 0) {
333
- varAccess += '[subIndex0]'
399
+ for (let i = 0; i < info.subscriptCount; i++) {
400
+ varAccess += `[subIndices[${i}]]`
334
401
  }
335
- if (info.subscriptCount > 1) {
336
- varAccess += '[subIndex1]'
337
- }
338
- if (info.subscriptCount > 2) {
339
- varAccess += '[subIndex2]'
340
- }
341
- let c = ''
342
- c += ` case ${info.varIndex}:\n`
343
- c += ` outputVar(${varAccess});\n`
344
- c += ` break;`
345
- return c
402
+ return `\
403
+ case ${info.varIndex}:
404
+ outputVar(${varAccess});
405
+ break;`
346
406
  })
347
- const section = R.pipe(code, lines)
407
+ const section = R.pipe(outputVars, code, lines)
348
408
  return section(varIndexInfo)
349
409
  }
350
410
  function inputsFromStringImpl() {
@@ -353,7 +413,7 @@ ${postStep}
353
413
  let inputVars = ''
354
414
  if (spec.inputVars && spec.inputVars.length > 0) {
355
415
  let inputVarPtrs = R.reduce((a, inputVar) => R.concat(a, ` &${inputVar},\n`), '', spec.inputVars)
356
- inputVars = `
416
+ inputVars = `\
357
417
  static double* inputVarPtrs[] = {\n${inputVarPtrs} };
358
418
  char* inputs = (char*)inputData;
359
419
  char* token = strtok(inputs, " ");
@@ -366,24 +426,53 @@ ${postStep}
366
426
  *inputVarPtrs[modelVarIndex] = value;
367
427
  }
368
428
  token = strtok(NULL, " ");
369
- }
370
- `
429
+ }`
371
430
  }
372
431
  return inputVars
373
432
  }
374
433
  function inputsFromBufferImpl() {
375
- let inputVars = ''
434
+ let inputVars = []
376
435
  if (spec.inputVars && spec.inputVars.length > 0) {
377
- inputVars += '\n'
378
436
  for (let i = 0; i < spec.inputVars.length; i++) {
379
437
  const inputVar = spec.inputVars[i]
380
- inputVars += ` ${inputVar} = inputData[${i}];\n`
438
+ inputVars.push(` ${inputVar} = inputData[${i}];`)
381
439
  }
382
440
  }
383
- return inputVars
441
+ return inputVars.join('\n')
384
442
  }
385
- function headerTitle(varName) {
386
- return Model.vensimName(varName).replace(/"/g, '\\"')
443
+ function setLookupImpl(varIndexInfo, customLookups) {
444
+ // Emit `replaceLookup` calls for all lookups and data variables that can be overridden
445
+ // at runtime
446
+ let includeCase
447
+ if (Array.isArray(customLookups)) {
448
+ // Only include a case statement if the variable was explicitly included
449
+ // in the `customLookups` array in the spec file
450
+ const customLookupVarNames = customLookups.map(varName => {
451
+ // The developer might specify a variable name that includes subscripts,
452
+ // but we will ignore the subscript part and only match on the base name
453
+ return canonicalVensimName(varName.split('[')[0])
454
+ })
455
+ includeCase = varName => customLookupVarNames.includes(varName)
456
+ } else {
457
+ // Include a case statement for all lookup and data variables
458
+ includeCase = () => true
459
+ }
460
+ const lookupAndDataVars = R.filter(info => {
461
+ return (info.varType === 'lookup' || info.varType === 'data') && includeCase(info.varName)
462
+ })
463
+ const code = R.map(info => {
464
+ let lookupVar = info.varName
465
+ for (let i = 0; i < info.subscriptCount; i++) {
466
+ lookupVar += `[subIndices[${i}]]`
467
+ }
468
+ let c = ''
469
+ c += ` case ${info.varIndex}:\n`
470
+ c += ` replaceLookup(&${lookupVar}, points, numPoints);\n`
471
+ c += ` break;`
472
+ return c
473
+ })
474
+ const section = R.pipe(lookupAndDataVars, code, lines)
475
+ return section(varIndexInfo)
387
476
  }
388
477
 
389
478
  return {