@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 CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.17",
3
+ "version": "0.7.19",
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
 
@@ -20,11 +19,7 @@ let codeGenerator = (parsedModel, opts) => {
20
19
  let outputAllVars = spec.outputVarNames === undefined || spec.outputVarNames.length === 0
21
20
  // Function to generate a section of the code
22
21
  let generateSection = R.map(v => {
23
- if (parsedModel.kind === 'vensim-legacy') {
24
- return new EquationGen(v, extData, directData, mode, modelDirname).generate()
25
- } else {
26
- return generateEquation(v, mode, extData, directData, modelDirname)
27
- }
22
+ return generateEquation(v, mode, extData, directData, modelDirname, 'c')
28
23
  })
29
24
  let section = R.pipe(generateSection, R.flatten, lines)
30
25
  function generate() {
@@ -86,51 +81,54 @@ ${section(Model.dataVars())}
86
81
  //
87
82
  function emitInitLookupsCode() {
88
83
  mode = 'init-lookups'
89
- let code = `// Internal state
84
+ let code = `\
85
+ // Internal state
90
86
  bool lookups_initialized = false;
91
87
  bool data_initialized = false;
88
+
92
89
  `
93
90
  code += chunkedFunctions(
94
91
  'initLookups',
95
92
  Model.lookupVars(),
96
- ` // Initialize lookups.
97
- if (!lookups_initialized) {
98
- `,
99
- ` lookups_initialized = true;
100
- }
101
- `
93
+ `\
94
+ // Initialize lookups.
95
+ if (lookups_initialized) {
96
+ return;
97
+ }`,
98
+ ' lookups_initialized = true;'
102
99
  )
103
100
  code += chunkedFunctions(
104
101
  'initData',
105
102
  Model.dataVars(),
106
- ` // Initialize data.
107
- if (!data_initialized) {
108
- `,
109
- ` data_initialized = true;
110
- }
111
- `
103
+ `\
104
+ // Initialize data.
105
+ if (data_initialized) {
106
+ return;
107
+ }`,
108
+ ' data_initialized = true;'
112
109
  )
113
110
  return code
114
111
  }
115
112
 
116
113
  function emitInitConstantsCode() {
117
114
  mode = 'init-constants'
118
- return `
119
- ${chunkedFunctions('initConstants', Model.constVars(), ' // Initialize constants.', ' initLookups();\n initData();')}
120
- `
115
+ return chunkedFunctions(
116
+ 'initConstants',
117
+ Model.constVars(),
118
+ ' // Initialize constants.',
119
+ ' initLookups();\n initData();'
120
+ )
121
121
  }
122
122
 
123
123
  function emitInitLevelsCode() {
124
124
  mode = 'init-levels'
125
- return `
126
- ${chunkedFunctions(
127
- 'initLevels',
128
- Model.initVars(),
129
- `
125
+ return chunkedFunctions(
126
+ 'initLevels',
127
+ Model.initVars(),
128
+ `\
130
129
  // Initialize variables with initialization values, such as levels, and the variables they depend on.
131
130
  _time = _initial_time;`
132
- )}
133
- `
131
+ )
134
132
  }
135
133
 
136
134
  //
@@ -139,23 +137,85 @@ ${chunkedFunctions(
139
137
  function emitEvalCode() {
140
138
  mode = 'eval'
141
139
 
142
- return `
143
- ${chunkedFunctions('evalAux', Model.auxVars(), ' // Evaluate auxiliaries in order from the bottom up.')}
144
-
145
- ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}
146
- `
140
+ return `\
141
+ ${chunkedFunctions('evalAux', Model.auxVars(), ' // Evaluate auxiliaries in order from the bottom up.')}\
142
+ ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}`
147
143
  }
148
144
 
149
145
  //
150
146
  // Input/output section
151
147
  //
152
148
  function emitIOCode() {
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
153
171
  let headerVarNames = outputAllVars ? expandedVarNames(true) : spec.outputVarNames
154
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
+
155
193
  mode = 'io'
156
- return `void setInputs(const char* inputData) {${inputsFromStringImpl()}}
194
+ return `\
195
+ void setInputs(const char* inputData) {
196
+ ${inputsFromStringImpl()}
197
+ }
157
198
 
158
- void setInputsFromBuffer(double* inputData) {${inputsFromBufferImpl()}}
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
+ }
215
+
216
+ void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints) {
217
+ ${setLookupBody}
218
+ }
159
219
 
160
220
  const char* getHeader() {
161
221
  return "${R.map(varName => varName.replace(/"/g, '\\"'), headerVarNames).join('\\t')}";
@@ -165,14 +225,8 @@ void storeOutputData() {
165
225
  ${specOutputSection(outputVarIds)}
166
226
  }
167
227
 
168
- void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
169
- #if SDE_USE_OUTPUT_INDICES
170
- switch (varIndex) {
171
- ${fullOutputSection(Model.varIndexInfo())}
172
- default:
173
- break;
174
- }
175
- #endif
228
+ void storeOutput(size_t varIndex, size_t* subIndices) {
229
+ ${storeOutputBody}
176
230
  }
177
231
  `
178
232
  }
@@ -183,9 +237,9 @@ ${fullOutputSection(Model.varIndexInfo())}
183
237
  function chunkedFunctions(name, vars, preStep, postStep) {
184
238
  // Emit one function for each chunk
185
239
  let func = (chunk, idx) => {
186
- return `
240
+ return `\
187
241
  void ${name}${idx}() {
188
- ${section(chunk)}
242
+ ${section(chunk)}
189
243
  }
190
244
  `
191
245
  }
@@ -213,22 +267,25 @@ void ${name}${idx}() {
213
267
  chunks = [vars]
214
268
  }
215
269
 
216
- if (!preStep) {
217
- preStep = ''
270
+ let funcsPart = funcs(chunks)
271
+ let callsPart = funcCalls(chunks)
272
+
273
+ let out = ''
274
+ if (funcsPart.length > 0) {
275
+ out += funcsPart + '\n'
218
276
  }
219
- if (!postStep) {
220
- postStep = ''
277
+ out += `void ${name}() {\n`
278
+ if (preStep) {
279
+ out += preStep + '\n'
221
280
  }
222
-
223
- return `
224
- ${funcs(chunks)}
225
-
226
- void ${name}() {
227
- ${preStep}
228
- ${funcCalls(chunks)}
229
- ${postStep}
230
- }
231
- `
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
232
289
  }
233
290
 
234
291
  //
@@ -276,9 +333,6 @@ ${postStep}
276
333
  } else {
277
334
  decls = `const int numOutputs = ${spec.outputVars.length};`
278
335
  }
279
- decls += `\n#define SDE_USE_OUTPUT_INDICES 0`
280
- decls += `\n#define SDE_MAX_OUTPUT_INDICES 1000`
281
- decls += `\nconst int maxOutputIndices = SDE_USE_OUTPUT_INDICES ? SDE_MAX_OUTPUT_INDICES : 0;`
282
336
  return decls
283
337
  }
284
338
  function arrayDimensionsSection() {
@@ -313,31 +367,44 @@ ${postStep}
313
367
  // Input/output section helpers
314
368
  //
315
369
  function specOutputSection(varNames) {
316
- // 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.
317
372
  let code = R.map(varName => ` outputVar(${varName});`)
318
373
  let section = R.pipe(code, lines)
319
374
  return section(varNames)
320
375
  }
321
- function fullOutputSection(varIndexInfo) {
322
- // 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
+ })
323
397
  const code = R.map(info => {
324
398
  let varAccess = info.varName
325
- if (info.subscriptCount > 0) {
326
- varAccess += '[subIndex0]'
327
- }
328
- if (info.subscriptCount > 1) {
329
- varAccess += '[subIndex1]'
399
+ for (let i = 0; i < info.subscriptCount; i++) {
400
+ varAccess += `[subIndices[${i}]]`
330
401
  }
331
- if (info.subscriptCount > 2) {
332
- varAccess += '[subIndex2]'
333
- }
334
- let c = ''
335
- c += ` case ${info.varIndex}:\n`
336
- c += ` outputVar(${varAccess});\n`
337
- c += ` break;`
338
- return c
402
+ return `\
403
+ case ${info.varIndex}:
404
+ outputVar(${varAccess});
405
+ break;`
339
406
  })
340
- const section = R.pipe(code, lines)
407
+ const section = R.pipe(outputVars, code, lines)
341
408
  return section(varIndexInfo)
342
409
  }
343
410
  function inputsFromStringImpl() {
@@ -346,7 +413,7 @@ ${postStep}
346
413
  let inputVars = ''
347
414
  if (spec.inputVars && spec.inputVars.length > 0) {
348
415
  let inputVarPtrs = R.reduce((a, inputVar) => R.concat(a, ` &${inputVar},\n`), '', spec.inputVars)
349
- inputVars = `
416
+ inputVars = `\
350
417
  static double* inputVarPtrs[] = {\n${inputVarPtrs} };
351
418
  char* inputs = (char*)inputData;
352
419
  char* token = strtok(inputs, " ");
@@ -359,21 +426,53 @@ ${postStep}
359
426
  *inputVarPtrs[modelVarIndex] = value;
360
427
  }
361
428
  token = strtok(NULL, " ");
362
- }
363
- `
429
+ }`
364
430
  }
365
431
  return inputVars
366
432
  }
367
433
  function inputsFromBufferImpl() {
368
- let inputVars = ''
434
+ let inputVars = []
369
435
  if (spec.inputVars && spec.inputVars.length > 0) {
370
- inputVars += '\n'
371
436
  for (let i = 0; i < spec.inputVars.length; i++) {
372
437
  const inputVar = spec.inputVars[i]
373
- inputVars += ` ${inputVar} = inputData[${i}];\n`
438
+ inputVars.push(` ${inputVar} = inputData[${i}];`)
374
439
  }
375
440
  }
376
- return inputVars
441
+ return inputVars.join('\n')
442
+ }
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)
377
476
  }
378
477
 
379
478
  return {