@sdeverywhere/compile 0.7.26 → 0.7.27

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.26",
3
+ "version": "0.7.27",
4
4
  "description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -146,6 +146,24 @@ ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}`
146
146
  // Input/output section
147
147
  //
148
148
  function emitIOCode() {
149
+ // Configure the body of the `setConstant` function depending on the value
150
+ // of the `customConstants` property in the spec file
151
+ let setConstantBody
152
+ if (spec.customConstants === true || Array.isArray(spec.customConstants)) {
153
+ setConstantBody = `\
154
+ switch (varIndex) {
155
+ ${setConstantImpl(Model.varIndexInfo(), spec.customConstants)}
156
+ default:
157
+ fprintf(stderr, "No constant found for var index %zu in setConstant\\n", varIndex);
158
+ break;
159
+ }`
160
+ } else {
161
+ let msg = 'The setConstant function was not enabled for the generated model. '
162
+ msg += 'Set the customConstants property in the spec/config file to allow for overriding constants at runtime.'
163
+ setConstantBody = `\
164
+ fprintf(stderr, "${msg}\\n");`
165
+ }
166
+
149
167
  // Configure the body of the `setLookup` function depending on the value
150
168
  // of the `customLookups` property in the spec file
151
169
  // TODO: The fprintf calls should be replaced with a mechanism that throws
@@ -200,12 +218,12 @@ ${customOutputSection(Model.varIndexInfo(), spec.customOutputs)}
200
218
 
201
219
  mode = 'io'
202
220
  return `\
203
- void setInputs(const char* inputData) {
204
- ${inputsFromStringImpl()}
221
+ void setInputs(double* inputValues, int32_t* inputIndices) {
222
+ ${setInputsImpl()}
205
223
  }
206
224
 
207
- void setInputsFromBuffer(double* inputData) {
208
- ${inputsFromBufferImpl()}
225
+ void setConstant(size_t varIndex, size_t* subIndices, double value) {
226
+ ${setConstantBody}
209
227
  }
210
228
 
211
229
  void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints) {
@@ -322,13 +340,19 @@ ${section(chunk)}
322
340
  }
323
341
  function internalVarsSection() {
324
342
  // Declare internal variables to run the model.
325
- let decls
343
+ let numInputsDecl
344
+ if (spec.inputVars && spec.inputVars.length > 0) {
345
+ numInputsDecl = `const int numInputs = ${spec.inputVars.length};`
346
+ } else {
347
+ numInputsDecl = `const int numInputs = 0;`
348
+ }
349
+ let numOutputsDecl
326
350
  if (outputAllVars) {
327
- decls = `const int numOutputs = ${expandedVarNames().length};`
351
+ numOutputsDecl = `const int numOutputs = ${expandedVarNames().length};`
328
352
  } else {
329
- decls = `const int numOutputs = ${spec.outputVars.length};`
353
+ numOutputsDecl = `const int numOutputs = ${spec.outputVars.length};`
330
354
  }
331
- return decls
355
+ return `${numInputsDecl}\n${numOutputsDecl}`
332
356
  }
333
357
  function arrayDimensionsSection() {
334
358
  // Emit a declaration for each array dimension's index numbers.
@@ -402,38 +426,64 @@ ${section(chunk)}
402
426
  const section = R.pipe(outputVars, code, lines)
403
427
  return section(varIndexInfo)
404
428
  }
405
- function inputsFromStringImpl() {
406
- // If there was an I/O spec file, then emit code to parse input variables.
407
- // The user can replace this with a parser for a different serialization format.
408
- let inputVars = ''
409
- if (spec.inputVars && spec.inputVars.length > 0) {
410
- let inputVarPtrs = R.reduce((a, inputVar) => R.concat(a, ` &${inputVar},\n`), '', spec.inputVars)
411
- inputVars = `\
412
- static double* inputVarPtrs[] = {\n${inputVarPtrs} };
413
- char* inputs = (char*)inputData;
414
- char* token = strtok(inputs, " ");
415
- while (token) {
416
- char* p = strchr(token, ':');
417
- if (p) {
418
- *p = '\\0';
419
- int modelVarIndex = atoi(token);
420
- double value = atof(p+1);
421
- *inputVarPtrs[modelVarIndex] = value;
429
+ function setInputsImpl() {
430
+ if (!spec.inputVars || spec.inputVars.length === 0) {
431
+ return ''
422
432
  }
423
- token = strtok(NULL, " ");
424
- }`
433
+ // Build the pointer table for input variables
434
+ let inputVarPtrs = R.reduce((a, inputVar) => R.concat(a, ` &${inputVar},\n`), '', spec.inputVars)
435
+ return `\
436
+ static double* inputVarPtrs[] = {
437
+ ${inputVarPtrs} };
438
+ if (inputIndices == NULL) {
439
+ // When inputIndices is NULL, assume that inputValues contains all input values
440
+ // in the same order that the variables are defined in the model spec
441
+ for (size_t i = 0; i < numInputs; i++) {
442
+ *inputVarPtrs[i] = inputValues[i];
425
443
  }
426
- return inputVars
444
+ } else {
445
+ // When inputIndices is non-NULL, set the input values according to the indices
446
+ // in the inputIndices array, where each index corresponds to the index of the
447
+ // variable in the model spec
448
+ size_t numInputsToSet = (size_t)inputIndices[0];
449
+ for (size_t i = 0; i < numInputsToSet; i++) {
450
+ size_t inputVarIndex = (size_t)inputIndices[i + 1];
451
+ *inputVarPtrs[inputVarIndex] = inputValues[i];
452
+ }
453
+ }`
427
454
  }
428
- function inputsFromBufferImpl() {
429
- let inputVars = []
430
- if (spec.inputVars && spec.inputVars.length > 0) {
431
- for (let i = 0; i < spec.inputVars.length; i++) {
432
- const inputVar = spec.inputVars[i]
433
- inputVars.push(` ${inputVar} = inputData[${i}];`)
434
- }
455
+ function setConstantImpl(varIndexInfo, customConstants) {
456
+ // Emit case statements for all const variables that can be overridden at runtime
457
+ let includeCase
458
+ if (Array.isArray(customConstants)) {
459
+ // Only include a case statement if the variable was explicitly included
460
+ // in the `customConstants` array in the spec file
461
+ const customConstantVarNames = customConstants.map(varName => {
462
+ // The developer might specify a variable name that includes subscripts,
463
+ // but we will ignore the subscript part and only match on the base name
464
+ return canonicalVensimName(varName.split('[')[0])
465
+ })
466
+ includeCase = varName => customConstantVarNames.includes(varName)
467
+ } else {
468
+ // Include a case statement for all constant variables
469
+ includeCase = () => true
435
470
  }
436
- return inputVars.join('\n')
471
+ const constVars = R.filter(info => {
472
+ return info.varType === 'const' && includeCase(info.varName)
473
+ })
474
+ const code = R.map(info => {
475
+ let constVar = info.varName
476
+ for (let i = 0; i < info.subscriptCount; i++) {
477
+ constVar += `[subIndices[${i}]]`
478
+ }
479
+ let c = ''
480
+ c += ` case ${info.varIndex}:\n`
481
+ c += ` ${constVar} = value;\n`
482
+ c += ` break;`
483
+ return c
484
+ })
485
+ const section = R.pipe(constVars, code, lines)
486
+ return section(varIndexInfo)
437
487
  }
438
488
  function setLookupImpl(varIndexInfo, customLookups) {
439
489
  // Emit case statements for all lookups and data variables that can be overridden
@@ -241,6 +241,27 @@ ${chunkedFunctions('evalLevels', true, Model.levelVars(), ' // Evaluate levels'
241
241
  function emitIOCode() {
242
242
  mode = 'io'
243
243
 
244
+ // Configure the body of the `setConstant` function depending on the value
245
+ // of the `customConstants` property in the spec file
246
+ let setConstantBody
247
+ if (spec.customConstants === true || Array.isArray(spec.customConstants)) {
248
+ setConstantBody = `\
249
+ if (!varSpec) {
250
+ throw new Error('Got undefined varSpec in setConstant');
251
+ }
252
+ const varIndex = varSpec.varIndex;
253
+ const subs = varSpec.subscriptIndices;
254
+ switch (varIndex) {
255
+ ${setConstantImpl(Model.varIndexInfo(), spec.customConstants)}
256
+ default:
257
+ throw new Error(\`No constant found for var index \${varIndex} in setConstant\`);
258
+ }`
259
+ } else {
260
+ let msg = 'The setConstant function was not enabled for the generated model. '
261
+ msg += 'Set the customConstants property in the spec/config file to allow for overriding constants at runtime.'
262
+ setConstantBody = ` throw new Error('${msg}');`
263
+ }
264
+
244
265
  // Configure the body of the `setLookup` function depending on the value
245
266
  // of the `customLookups` property in the spec file
246
267
  let setLookupBody
@@ -312,6 +333,10 @@ ${customOutputSection(Model.varIndexInfo(), spec.customOutputs)}
312
333
  return `\
313
334
  /*export*/ function setInputs(valueAtIndex /*: (index: number) => number*/) {${inputsFromBufferImpl()}}
314
335
 
336
+ /*export*/ function setConstant(varSpec /*: VarSpec*/, value /*: number*/) {
337
+ ${setConstantBody}
338
+ }
339
+
315
340
  /*export*/ function setLookup(varSpec /*: VarSpec*/, points /*: Float64Array | undefined*/) {
316
341
  ${setLookupBody}
317
342
  }
@@ -521,6 +546,39 @@ ${section(chunk)}
521
546
  }
522
547
  return inputVars
523
548
  }
549
+ function setConstantImpl(varIndexInfo, customConstants) {
550
+ // Emit case statements for all const variables that can be overridden at runtime
551
+ let overrideAllowed
552
+ if (Array.isArray(customConstants)) {
553
+ // Only include a case statement if the variable was explicitly included
554
+ // in the `customConstants` array in the spec file
555
+ const customConstantVarNames = customConstants.map(varName => {
556
+ // The developer might specify a variable name that includes subscripts,
557
+ // but we will ignore the subscript part and only match on the base name
558
+ return canonicalVensimName(varName.split('[')[0])
559
+ })
560
+ overrideAllowed = varName => customConstantVarNames.includes(varName)
561
+ } else {
562
+ // Include a case statement for all constant variables
563
+ overrideAllowed = () => true
564
+ }
565
+ const constVars = R.filter(info => {
566
+ return info.varType === 'const' && overrideAllowed(info.varName)
567
+ })
568
+ const code = R.map(info => {
569
+ let constVar = info.varName
570
+ for (let i = 0; i < info.subscriptCount; i++) {
571
+ constVar += `[subs[${i}]]`
572
+ }
573
+ let c = ''
574
+ c += ` case ${info.varIndex}:\n`
575
+ c += ` ${constVar} = value;\n`
576
+ c += ` break;`
577
+ return c
578
+ })
579
+ const section = R.pipe(constVars, code, lines)
580
+ return section(varIndexInfo)
581
+ }
524
582
  function setLookupImpl(varIndexInfo, customLookups) {
525
583
  // Emit case statements for all lookups and data variables that can be overridden
526
584
  // at runtime
@@ -605,6 +663,7 @@ export default async function () {
605
663
 
606
664
  setTime,
607
665
  setInputs,
666
+ setConstant,
608
667
  setLookup,
609
668
 
610
669
  storeOutputs,