@sdeverywhere/compile 0.7.5 → 0.7.7

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/README.md CHANGED
@@ -1,11 +1,38 @@
1
1
  # @sdeverywhere/compile
2
2
 
3
- This package contains the core SDEverywhere compiler that takes a Vensim model
4
- as input and generates C code as output.
3
+ This package contains the core [SDEverywhere](https://github.com/climateinteractive/SDEverywhere) compiler that takes a Vensim model as input and generates C code as output.
4
+
5
+ ## Quick Start
6
+
7
+ The best way to get started with SDEverywhere is to follow the [Quick Start](https://github.com/climateinteractive/SDEverywhere#quick-start) instructions.
8
+ If you follow those instructions, the `@sdeverywhere/cli` package will be added to your project automatically, and that package uses `@sdeverywhere/compile` as an implementation detail.
9
+ Therefore, most users do not need to install this package directly.
10
+
11
+ ## Install
12
+
13
+ As noted above, most users do not need to install this package directly, but for more advanced use cases, it can be installed as follows.
14
+
15
+ ```sh
16
+ # npm
17
+ npm install --save-dev @sdeverywhere/compile
18
+
19
+ # pnpm
20
+ pnpm add -D @sdeverywhere/compile
21
+
22
+ # yarn
23
+ yarn add -D @sdeverywhere/compile
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Most users do not need to interact with the `@sdeverywhere/compile` package directly; it is primarily used in the implementation of the `@sdeverywhere/cli` package and `sde` command line tool.
29
+
30
+ More usage details will be included here at a later time when the interfaces stabilize.
5
31
 
6
32
  ## Documentation
7
33
 
8
- TODO
34
+ The `compile` package is currently treated as an implementation detail of the `cli` package.
35
+ As such, there is no public API documentation at this time, but we hope to expose a public API once the interfaces stabilize.
9
36
 
10
37
  ## License
11
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/compile",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,7 +1,7 @@
1
1
  import util from 'util'
2
2
  import B from 'bufx'
3
3
  import { parse as parseCsv } from 'csv-parse/sync'
4
- import R from 'ramda'
4
+ import * as R from 'ramda'
5
5
  import split from 'split-string'
6
6
  import XLSX from 'xlsx'
7
7
 
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs'
2
2
  import B from 'bufx'
3
3
  import byline from 'byline'
4
- import R from 'ramda'
4
+ import * as R from 'ramda'
5
5
 
6
6
  import { canonicalVensimName } from './helpers.js'
7
7
 
@@ -1,7 +1,7 @@
1
1
  import util from 'util'
2
2
  import B from 'bufx'
3
3
  import yaml from 'js-yaml'
4
- import R from 'ramda'
4
+ import * as R from 'ramda'
5
5
  import { canonicalName, asort, vlog } from './helpers.js'
6
6
 
7
7
  // A subscript is a dimension or an index.
@@ -1,4 +1,4 @@
1
- import R from 'ramda'
1
+ import * as R from 'ramda'
2
2
 
3
3
  import { asort, lines, strlist, abend, mapIndexed } from '../_shared/helpers.js'
4
4
  import { sub, allDimensions, allMappings, subscriptFamilies } from '../_shared/subscript.js'
@@ -163,7 +163,17 @@ const char* getHeader() {
163
163
  }
164
164
 
165
165
  void storeOutputData() {
166
- ${outputSection(outputVars)}
166
+ ${specOutputSection(outputVars)}
167
+ }
168
+
169
+ void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
170
+ #if SDE_USE_OUTPUT_INDICES
171
+ switch (varIndex) {
172
+ ${fullOutputSection(Model.varIndexInfo())}
173
+ default:
174
+ break;
175
+ }
176
+ #endif
167
177
  }
168
178
  `
169
179
  }
@@ -250,11 +260,16 @@ ${postStep}
250
260
  }
251
261
  function internalVarsSection() {
252
262
  // Declare internal variables to run the model.
263
+ let decls
253
264
  if (outputAllVars) {
254
- return `const int numOutputs = ${expandedVarNames().length};`
265
+ decls = `const int numOutputs = ${expandedVarNames().length};`
255
266
  } else {
256
- return `const int numOutputs = ${spec.outputVars.length};`
267
+ decls = `const int numOutputs = ${spec.outputVars.length};`
257
268
  }
269
+ decls += `\n#define SDE_USE_OUTPUT_INDICES 0`
270
+ decls += `\n#define SDE_MAX_OUTPUT_INDICES 1000`
271
+ decls += `\nconst int maxOutputIndices = SDE_USE_OUTPUT_INDICES ? SDE_MAX_OUTPUT_INDICES : 0;`
272
+ return decls
258
273
  }
259
274
  function arrayDimensionsSection() {
260
275
  // Emit a declaration for each array dimension's index numbers.
@@ -312,12 +327,34 @@ ${postStep}
312
327
  //
313
328
  // Input/output section helpers
314
329
  //
315
- function outputSection(varNames) {
330
+ function specOutputSection(varNames) {
316
331
  // Emit output calls using varNames in C format.
317
332
  let code = R.map(varName => ` outputVar(${varName});`)
318
333
  let section = R.pipe(code, lines)
319
334
  return section(varNames)
320
335
  }
336
+ function fullOutputSection(varIndexInfo) {
337
+ // Emit output calls for all variables.
338
+ const code = R.map(info => {
339
+ let varAccess = info.varName
340
+ if (info.subscriptCount > 0) {
341
+ varAccess += '[subIndex0]'
342
+ }
343
+ if (info.subscriptCount > 1) {
344
+ varAccess += '[subIndex1]'
345
+ }
346
+ if (info.subscriptCount > 2) {
347
+ varAccess += '[subIndex2]'
348
+ }
349
+ let c = ''
350
+ c += ` case ${info.varIndex}:\n`
351
+ c += ` outputVar(${varAccess});\n`
352
+ c += ` break;`
353
+ return c
354
+ })
355
+ const section = R.pipe(code, lines)
356
+ return section(varIndexInfo)
357
+ }
321
358
  function inputsFromStringImpl() {
322
359
  // If there was an I/O spec file, then emit code to parse input variables.
323
360
  // The user can replace this with a parser for a different serialization format.
@@ -1,6 +1,6 @@
1
1
  import path from 'path'
2
2
  import { ModelLexer, ModelParser } from 'antlr4-vensim'
3
- import R from 'ramda'
3
+ import * as R from 'ramda'
4
4
  import XLSX from 'xlsx'
5
5
 
6
6
  import {
@@ -1,4 +1,4 @@
1
- import R from 'ramda'
1
+ import * as R from 'ramda'
2
2
 
3
3
  import { canonicalName } from '../_shared/helpers.js'
4
4
  import { sub, isDimension } from '../_shared/subscript.js'
@@ -1,5 +1,5 @@
1
1
  import { ModelParser } from 'antlr4-vensim'
2
- import R from 'ramda'
2
+ import * as R from 'ramda'
3
3
 
4
4
  import {
5
5
  canonicalName,
@@ -1,6 +1,6 @@
1
1
  import B from 'bufx'
2
2
  import yaml from 'js-yaml'
3
- import R from 'ramda'
3
+ import * as R from 'ramda'
4
4
 
5
5
  import { decanonicalize, isIterable, listConcat, strlist, vlog, vsort } from '../_shared/helpers.js'
6
6
  import {
@@ -330,8 +330,14 @@ function removeUnusedVariables(spec) {
330
330
  if (!referencedRefIds.has(refId)) {
331
331
  referencedRefIds.add(refId)
332
332
  const refVar = varWithRefId(refId)
333
- recordUsedVariable(refVar)
334
- recordRefsOfVariable(refVar)
333
+ if (refVar) {
334
+ recordUsedVariable(refVar)
335
+ recordRefsOfVariable(refVar)
336
+ } else {
337
+ console.error(`No var found for ${refId}`)
338
+ console.error(v)
339
+ process.exit(1)
340
+ }
335
341
  }
336
342
  }
337
343
  }
@@ -1010,6 +1016,105 @@ function printDepsGraph(graph, varType) {
1010
1016
  console.error(`${dep[0]} → ${dep[1]}`)
1011
1017
  }
1012
1018
  }
1019
+
1020
+ function allListedVars() {
1021
+ // Put variables into the order that they are evaluated by SDE in the generated model
1022
+ let vars = []
1023
+ vars.push(...constVars())
1024
+ vars.push(...lookupVars())
1025
+ vars.push(...dataVars())
1026
+ vars.push(varWithName('_time'))
1027
+ vars.push(...initVars())
1028
+ vars.push(...auxVars())
1029
+ // TODO: Also levelVars not covered by initVars?
1030
+
1031
+ // Filter out data/lookup variables and variables that are generated/used internally
1032
+ const isInternal = v => {
1033
+ return v.refId.startsWith('__level') || v.refId.startsWith('__aux')
1034
+ }
1035
+
1036
+ return R.filter(v => !isInternal(v), vars)
1037
+ }
1038
+
1039
+ function filteredListedVars() {
1040
+ // Extract a subset of the available info for each variable and sort all variables
1041
+ // according to the order that they are evaluated by SDE in the generated model
1042
+ return R.map(v => filterVar(v), allListedVars())
1043
+ }
1044
+
1045
+ function varIndexInfoMap() {
1046
+ // Return a map containing information for each listed variable:
1047
+ // varName
1048
+ // varIndex
1049
+ // subscriptCount
1050
+
1051
+ // Get the filtered variables in the order that they are evaluated by SDE in the
1052
+ // generated model
1053
+ const sortedVars = filteredListedVars()
1054
+
1055
+ // Get the set of unique variable names, and assign a 1-based index
1056
+ // to each; this matches the index number used in `storeOutput()`
1057
+ // in the generated C code
1058
+ const infoMap = new Map()
1059
+ let varIndex = 1
1060
+ for (const v of sortedVars) {
1061
+ if (v.varType === 'data' || v.varType === 'lookup') {
1062
+ // Omit the index for data and lookup variables; at this time, the data for these
1063
+ // cannot be output like for other types of variables
1064
+ continue
1065
+ }
1066
+ const varName = v.varName
1067
+ if (!infoMap.get(varName)) {
1068
+ infoMap.set(varName, {
1069
+ varName,
1070
+ varIndex,
1071
+ subscriptCount: v.families ? v.families.length : 0
1072
+ })
1073
+ varIndex++
1074
+ }
1075
+ }
1076
+
1077
+ return infoMap
1078
+ }
1079
+
1080
+ function varIndexInfo() {
1081
+ // Return an array, sorted by `varName`, containing information for each
1082
+ // listed variable:
1083
+ // varName
1084
+ // varIndex
1085
+ // subscriptCount
1086
+ return Array.from(varIndexInfoMap().values())
1087
+ }
1088
+
1089
+ function jsonList() {
1090
+ // Return a stringified JSON object containing variable and subscript information
1091
+ // for the model.
1092
+
1093
+ // Get the set of available subscripts
1094
+ const allDims = [...allDimensions()]
1095
+ const sortedDims = allDims.sort((a, b) => a.name.localeCompare(b.name))
1096
+
1097
+ // Extract a subset of the available info for each variable and put them in eval order
1098
+ const sortedVars = filteredListedVars()
1099
+
1100
+ // Assign a 1-based index for each variable that has data that can be accessed.
1101
+ // This matches the index number used in `storeOutput()` in the generated C code.
1102
+ const infoMap = varIndexInfoMap()
1103
+ for (const v of sortedVars) {
1104
+ const varInfo = infoMap.get(v.varName)
1105
+ if (varInfo) {
1106
+ v.varIndex = varInfo.varIndex
1107
+ }
1108
+ }
1109
+
1110
+ // Convert to JSON
1111
+ const obj = {
1112
+ dimensions: sortedDims,
1113
+ variables: sortedVars
1114
+ }
1115
+ return JSON.stringify(obj, null, 2)
1116
+ }
1117
+
1013
1118
  export default {
1014
1119
  addConstantExpr,
1015
1120
  addEquation,
@@ -1026,6 +1131,7 @@ export default {
1026
1131
  initVars,
1027
1132
  isInputVar,
1028
1133
  isNonAtoAName,
1134
+ jsonList,
1029
1135
  levelVars,
1030
1136
  lookupVars,
1031
1137
  printRefGraph,
@@ -1036,6 +1142,7 @@ export default {
1036
1142
  refIdsWithName,
1037
1143
  splitRefId,
1038
1144
  variables,
1145
+ varIndexInfo,
1039
1146
  varNames,
1040
1147
  varsWithName,
1041
1148
  varWithName,
@@ -1,6 +1,6 @@
1
1
  import path from 'path'
2
2
  import { ModelParser } from 'antlr4-vensim'
3
- import R from 'ramda'
3
+ import * as R from 'ramda'
4
4
  import XLSX from 'xlsx'
5
5
 
6
6
  import { cFunctionName, matchRegex, readCsv } from '../_shared/helpers.js'
@@ -1,4 +1,4 @@
1
- import R from 'ramda'
1
+ import * as R from 'ramda'
2
2
 
3
3
  import { canonicalName } from '../_shared/helpers.js'
4
4
  import { sub, isIndex, normalizeSubscripts } from '../_shared/subscript.js'
@@ -1,5 +1,5 @@
1
1
  import { ModelParser } from 'antlr4-vensim'
2
- import R from 'ramda'
2
+ import * as R from 'ramda'
3
3
 
4
4
  import { canonicalName, vlog, strlist, cartesianProductOf } from '../_shared/helpers.js'
5
5
  import {
@@ -17,7 +17,7 @@ import { generateCode } from './generate/code-gen.js'
17
17
  *
18
18
  * - If `operation` is 'generateC', the generated C code will be written to `buildDir`.
19
19
  * - If `operation` is 'printVarList', variables and subscripts will be written to
20
- * txt and yaml files under `buildDir`.
20
+ * txt, yaml, and json files under `buildDir`.
21
21
  * - If `operation` is 'printRefIdTest', reference identifiers will be printed to the console.
22
22
  * - If `operation` is 'convertNames', no output will be generated, but the results of model
23
23
  * analysis will be available.
@@ -85,6 +85,8 @@ export async function parseAndGenerate(input, spec, operation, modelDirname, mod
85
85
  writeOutput(`${modelName}_vars.yaml`, Model.yamlVarList())
86
86
  // Write subscripts to a YAML file.
87
87
  writeOutput(`${modelName}_subs.yaml`, yamlSubsList())
88
+ // Write variables and subscripts to a JSON file.
89
+ writeOutput(`${modelName}.json`, Model.jsonList())
88
90
  }
89
91
 
90
92
  return code
@@ -1,6 +1,6 @@
1
1
  import path from 'path'
2
2
  import B from 'bufx'
3
- import R from 'ramda'
3
+ import * as R from 'ramda'
4
4
  import { splitEquations, replaceDelimitedStrings } from '../_shared/helpers.js'
5
5
 
6
6
  export let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, outDecls = []) => {