@sdeverywhere/compile 0.7.0
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/LICENSE +21 -0
- package/README.md +12 -0
- package/package.json +40 -0
- package/src/_shared/helpers.js +351 -0
- package/src/_shared/read-dat.js +71 -0
- package/src/_shared/subscript.js +373 -0
- package/src/generate/code-gen.js +356 -0
- package/src/generate/equation-gen.js +1188 -0
- package/src/generate/loop-index-vars.js +27 -0
- package/src/generate/model-lhs-reader.js +88 -0
- package/src/index.js +8 -0
- package/src/model/equation-reader.js +708 -0
- package/src/model/expr-reader.js +202 -0
- package/src/model/model.js +1045 -0
- package/src/model/subscript-range-reader.js +123 -0
- package/src/model/toposort.js +100 -0
- package/src/model/var-name-reader.js +40 -0
- package/src/model/variable-reader.js +172 -0
- package/src/model/variable.js +111 -0
- package/src/parse/model-reader.js +141 -0
- package/src/parse/parser.js +32 -0
- package/src/parse-and-generate.js +114 -0
- package/src/preprocess/preprocessor.js +247 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import R from 'ramda'
|
|
2
|
+
|
|
3
|
+
import { asort, lines, strlist, abend, mapIndexed } from '../_shared/helpers.js'
|
|
4
|
+
import { sub, allDimensions, allMappings, subscriptFamilies } from '../_shared/subscript.js'
|
|
5
|
+
import Model from '../model/model.js'
|
|
6
|
+
|
|
7
|
+
import EquationGen from './equation-gen.js'
|
|
8
|
+
import ModelLHSReader from './model-lhs-reader.js'
|
|
9
|
+
|
|
10
|
+
export function generateCode(parseTree, opts) {
|
|
11
|
+
return codeGenerator(parseTree, opts).generate()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let codeGenerator = (parseTree, opts) => {
|
|
15
|
+
const { spec, operation, extData, directData, modelDirname } = opts
|
|
16
|
+
// Set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated.
|
|
17
|
+
let mode = ''
|
|
18
|
+
// Set to true to output all variables when there is no model run spec.
|
|
19
|
+
let outputAllVars
|
|
20
|
+
if (spec.outputVars && spec.outputVars.length > 0) {
|
|
21
|
+
outputAllVars = false
|
|
22
|
+
} else if (spec.outputVarNames && spec.outputVarNames.length > 0) {
|
|
23
|
+
outputAllVars = false
|
|
24
|
+
} else {
|
|
25
|
+
outputAllVars = true
|
|
26
|
+
}
|
|
27
|
+
// Function to generate a section of the code
|
|
28
|
+
let generateSection = R.map(v => new EquationGen(v, extData, directData, mode, modelDirname).generate())
|
|
29
|
+
let section = R.pipe(generateSection, R.flatten, lines)
|
|
30
|
+
function generate() {
|
|
31
|
+
// Read variables and subscript ranges from the model parse tree.
|
|
32
|
+
// This is the main entry point for code generation and is called just once.
|
|
33
|
+
try {
|
|
34
|
+
Model.read(parseTree, spec, extData, directData, modelDirname)
|
|
35
|
+
// In list mode, print variables to the console instead of generating code.
|
|
36
|
+
if (operation === 'printRefIdTest') {
|
|
37
|
+
Model.printRefIdTest()
|
|
38
|
+
} else if (operation === 'printRefGraph') {
|
|
39
|
+
Model.printRefGraph(opts.varname)
|
|
40
|
+
} else if (operation === 'convertNames') {
|
|
41
|
+
// Do not generate output, but leave the results of model analysis.
|
|
42
|
+
} else if (operation === 'generateC') {
|
|
43
|
+
// Generate code for each variable in the proper order.
|
|
44
|
+
let code = emitDeclCode()
|
|
45
|
+
code += emitInitLookupsCode()
|
|
46
|
+
code += emitInitConstantsCode()
|
|
47
|
+
code += emitInitLevelsCode()
|
|
48
|
+
code += emitEvalCode()
|
|
49
|
+
code += emitIOCode()
|
|
50
|
+
return code
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
abend(e)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Each code section follows in an outline of the generated model code.
|
|
58
|
+
|
|
59
|
+
//
|
|
60
|
+
// Declaration section
|
|
61
|
+
//
|
|
62
|
+
function emitDeclCode() {
|
|
63
|
+
mode = 'decl'
|
|
64
|
+
return `#include "sde.h"
|
|
65
|
+
|
|
66
|
+
// Model variables
|
|
67
|
+
${declSection()}
|
|
68
|
+
|
|
69
|
+
// Internal variables
|
|
70
|
+
${internalVarsSection()}
|
|
71
|
+
|
|
72
|
+
// Array dimensions
|
|
73
|
+
${arrayDimensionsSection()}
|
|
74
|
+
|
|
75
|
+
// Dimension mappings
|
|
76
|
+
${dimensionMappingsSection()}
|
|
77
|
+
|
|
78
|
+
// Lookup data arrays
|
|
79
|
+
${section(Model.lookupVars())}
|
|
80
|
+
${section(Model.dataVars())}
|
|
81
|
+
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// Initialization section
|
|
87
|
+
//
|
|
88
|
+
function emitInitLookupsCode() {
|
|
89
|
+
mode = 'init-lookups'
|
|
90
|
+
let code = `// Internal state
|
|
91
|
+
bool lookups_initialized = false;
|
|
92
|
+
bool data_initialized = false;
|
|
93
|
+
`
|
|
94
|
+
code += chunkedFunctions(
|
|
95
|
+
'initLookups',
|
|
96
|
+
Model.lookupVars(),
|
|
97
|
+
` // Initialize lookups.
|
|
98
|
+
if (!lookups_initialized) {
|
|
99
|
+
`,
|
|
100
|
+
` lookups_initialized = true;
|
|
101
|
+
}
|
|
102
|
+
`
|
|
103
|
+
)
|
|
104
|
+
code += chunkedFunctions(
|
|
105
|
+
'initData',
|
|
106
|
+
Model.dataVars(),
|
|
107
|
+
` // Initialize data.
|
|
108
|
+
if (!data_initialized) {
|
|
109
|
+
`,
|
|
110
|
+
` data_initialized = true;
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
)
|
|
114
|
+
return code
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function emitInitConstantsCode() {
|
|
118
|
+
mode = 'init-constants'
|
|
119
|
+
return `
|
|
120
|
+
${chunkedFunctions('initConstants', Model.constVars(), ' // Initialize constants.', ' initLookups();\n initData();')}
|
|
121
|
+
`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function emitInitLevelsCode() {
|
|
125
|
+
mode = 'init-levels'
|
|
126
|
+
return `
|
|
127
|
+
${chunkedFunctions(
|
|
128
|
+
'initLevels',
|
|
129
|
+
Model.initVars(),
|
|
130
|
+
`
|
|
131
|
+
// Initialize variables with initialization values, such as levels, and the variables they depend on.
|
|
132
|
+
_time = _initial_time;`
|
|
133
|
+
)}
|
|
134
|
+
`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//
|
|
138
|
+
// Evaluation section
|
|
139
|
+
//
|
|
140
|
+
function emitEvalCode() {
|
|
141
|
+
mode = 'eval'
|
|
142
|
+
|
|
143
|
+
return `
|
|
144
|
+
${chunkedFunctions('evalAux', Model.auxVars(), ' // Evaluate auxiliaries in order from the bottom up.')}
|
|
145
|
+
|
|
146
|
+
${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}
|
|
147
|
+
`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//
|
|
151
|
+
// Input/output section
|
|
152
|
+
//
|
|
153
|
+
function emitIOCode() {
|
|
154
|
+
let headerVars = outputAllVars ? expandedVarNames(true) : spec.outputVars
|
|
155
|
+
let outputVars = outputAllVars ? expandedVarNames() : spec.outputVars
|
|
156
|
+
mode = 'io'
|
|
157
|
+
return `void setInputs(const char* inputData) {${inputsFromStringImpl()}}
|
|
158
|
+
|
|
159
|
+
void setInputsFromBuffer(double* inputData) {${inputsFromBufferImpl()}}
|
|
160
|
+
|
|
161
|
+
const char* getHeader() {
|
|
162
|
+
return "${R.map(varName => headerTitle(varName), headerVars).join('\\t')}";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
void storeOutputData() {
|
|
166
|
+
${outputSection(outputVars)}
|
|
167
|
+
}
|
|
168
|
+
`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//
|
|
172
|
+
// Chunked function helper
|
|
173
|
+
//
|
|
174
|
+
function chunkedFunctions(name, vars, preStep, postStep) {
|
|
175
|
+
// Emit one function for each chunk
|
|
176
|
+
let func = (chunk, idx) => {
|
|
177
|
+
return `
|
|
178
|
+
void ${name}${idx}() {
|
|
179
|
+
${section(chunk)}
|
|
180
|
+
}
|
|
181
|
+
`
|
|
182
|
+
}
|
|
183
|
+
let funcs = R.pipe(mapIndexed(func), lines)
|
|
184
|
+
|
|
185
|
+
// Emit one roll-up function that calls the other chunk functions
|
|
186
|
+
let funcCall = (chunk, idx) => {
|
|
187
|
+
return ` ${name}${idx}();`
|
|
188
|
+
}
|
|
189
|
+
let funcCalls = R.pipe(mapIndexed(funcCall), lines)
|
|
190
|
+
|
|
191
|
+
// Break the vars into chunks of 30; this number was empirically
|
|
192
|
+
// determined by looking at runtime performance and memory usage
|
|
193
|
+
// of the En-ROADS model on various devices
|
|
194
|
+
let chunks = R.splitEvery(30, vars)
|
|
195
|
+
|
|
196
|
+
if (!preStep) {
|
|
197
|
+
preStep = ''
|
|
198
|
+
}
|
|
199
|
+
if (!postStep) {
|
|
200
|
+
postStep = ''
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return `
|
|
204
|
+
${funcs(chunks)}
|
|
205
|
+
|
|
206
|
+
void ${name}() {
|
|
207
|
+
${preStep}
|
|
208
|
+
${funcCalls(chunks)}
|
|
209
|
+
${postStep}
|
|
210
|
+
}
|
|
211
|
+
`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
//
|
|
215
|
+
// Declaration section helpers
|
|
216
|
+
//
|
|
217
|
+
function declSection() {
|
|
218
|
+
// Emit a declaration for each variable in the model.
|
|
219
|
+
let fixedDelayDecls = ''
|
|
220
|
+
let decl = v => {
|
|
221
|
+
// Build a C array declaration for the variable v.
|
|
222
|
+
// This uses the subscript family for each dimension, which may overallocate
|
|
223
|
+
// if the subscript is a subdimension.
|
|
224
|
+
let varType = v.isLookup() || v.isData() ? 'Lookup* ' : 'double '
|
|
225
|
+
let families = subscriptFamilies(v.subscripts)
|
|
226
|
+
if (v.isFixedDelay()) {
|
|
227
|
+
// Add the associated FixedDelay var decl.
|
|
228
|
+
fixedDelayDecls += `\nFixedDelay* ${v.fixedDelayVarName}${R.map(
|
|
229
|
+
family => `[${sub(family).size}]`,
|
|
230
|
+
families
|
|
231
|
+
).join('')};`
|
|
232
|
+
}
|
|
233
|
+
return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('')
|
|
234
|
+
}
|
|
235
|
+
// Non-apply-to-all variables are declared multiple times, but coalesce using uniq.
|
|
236
|
+
let decls = R.pipe(
|
|
237
|
+
R.map(v => `${decl(v)};`),
|
|
238
|
+
R.uniq,
|
|
239
|
+
asort,
|
|
240
|
+
lines
|
|
241
|
+
)
|
|
242
|
+
return decls(Model.allVars()) + fixedDelayDecls
|
|
243
|
+
}
|
|
244
|
+
function internalVarsSection() {
|
|
245
|
+
// Declare internal variables to run the model.
|
|
246
|
+
if (outputAllVars) {
|
|
247
|
+
return `const int numOutputs = ${expandedVarNames().length};`
|
|
248
|
+
} else {
|
|
249
|
+
return `const int numOutputs = ${spec.outputVars.length};`
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function arrayDimensionsSection() {
|
|
253
|
+
// Emit a declaration for each array dimension's index numbers.
|
|
254
|
+
// These index number arrays will be used to indirectly reference array elements.
|
|
255
|
+
// The indirection is required to support subdimensions that are a non-contiguous subset of the array elements.
|
|
256
|
+
let a = R.map(dim => `const size_t ${dim.name}[${dim.size}] = { ${indexNumberList(sub(dim.name).value)} };`)
|
|
257
|
+
let arrayDims = R.pipe(a, asort, lines)
|
|
258
|
+
return arrayDims(allDimensions())
|
|
259
|
+
}
|
|
260
|
+
function dimensionMappingsSection() {
|
|
261
|
+
// Emit a mapping array for each dimension mapping.
|
|
262
|
+
let a = R.map(m => {
|
|
263
|
+
return `const size_t __map${m.mapFrom}${m.mapTo}[${sub(m.mapTo).size}] = { ${indexNumberList(m.value)} };`
|
|
264
|
+
})
|
|
265
|
+
let mappingArrays = R.pipe(a, asort, lines)
|
|
266
|
+
return mappingArrays(allMappings())
|
|
267
|
+
}
|
|
268
|
+
function indexNumberList(indices) {
|
|
269
|
+
// Make a comma-delimited list of index numbers in the dimension working from the index names.
|
|
270
|
+
let a = R.map(indexName => sub(indexName).value, indices)
|
|
271
|
+
return strlist(a)
|
|
272
|
+
}
|
|
273
|
+
function expandedVarNames(vensimNames = false) {
|
|
274
|
+
// Return a list of var names for all variables except lookups and data variables.
|
|
275
|
+
// The names are in Vensim format if vensimNames is true, otherwise they are in C format.
|
|
276
|
+
// Expand subscripted vars into separate var names with each index.
|
|
277
|
+
function sortedVars() {
|
|
278
|
+
// Return a list of all vars sorted by the model LHS var name (without subscripts), case insensitive.
|
|
279
|
+
return R.sortBy(v => {
|
|
280
|
+
let modelLHSReader = new ModelLHSReader()
|
|
281
|
+
modelLHSReader.read(v.modelLHS)
|
|
282
|
+
return modelLHSReader.varName.toUpperCase()
|
|
283
|
+
}, Model.variables)
|
|
284
|
+
}
|
|
285
|
+
return R.uniq(
|
|
286
|
+
R.reduce(
|
|
287
|
+
(a, v) => {
|
|
288
|
+
if (v.varType !== 'lookup' && v.varType !== 'data' && v.includeInOutput) {
|
|
289
|
+
let modelLHSReader = new ModelLHSReader()
|
|
290
|
+
modelLHSReader.read(v.modelLHS)
|
|
291
|
+
if (vensimNames) {
|
|
292
|
+
return R.concat(a, modelLHSReader.names())
|
|
293
|
+
} else {
|
|
294
|
+
return R.concat(a, R.map(Model.cName, modelLHSReader.names()))
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
return a
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
[],
|
|
301
|
+
sortedVars()
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
//
|
|
306
|
+
// Input/output section helpers
|
|
307
|
+
//
|
|
308
|
+
function outputSection(varNames) {
|
|
309
|
+
// Emit output calls using varNames in C format.
|
|
310
|
+
let code = R.map(varName => ` outputVar(${varName});`)
|
|
311
|
+
let section = R.pipe(code, lines)
|
|
312
|
+
return section(varNames)
|
|
313
|
+
}
|
|
314
|
+
function inputsFromStringImpl() {
|
|
315
|
+
// If there was an I/O spec file, then emit code to parse input variables.
|
|
316
|
+
// The user can replace this with a parser for a different serialization format.
|
|
317
|
+
let inputVars = ''
|
|
318
|
+
if (spec.inputVars && spec.inputVars.length > 0) {
|
|
319
|
+
let inputVarPtrs = R.reduce((a, inputVar) => R.concat(a, ` &${inputVar},\n`), '', spec.inputVars)
|
|
320
|
+
inputVars = `
|
|
321
|
+
static double* inputVarPtrs[] = {\n${inputVarPtrs} };
|
|
322
|
+
char* inputs = (char*)inputData;
|
|
323
|
+
char* token = strtok(inputs, " ");
|
|
324
|
+
while (token) {
|
|
325
|
+
char* p = strchr(token, ':');
|
|
326
|
+
if (p) {
|
|
327
|
+
*p = '\\0';
|
|
328
|
+
int modelVarIndex = atoi(token);
|
|
329
|
+
double value = atof(p+1);
|
|
330
|
+
*inputVarPtrs[modelVarIndex] = value;
|
|
331
|
+
}
|
|
332
|
+
token = strtok(NULL, " ");
|
|
333
|
+
}
|
|
334
|
+
`
|
|
335
|
+
}
|
|
336
|
+
return inputVars
|
|
337
|
+
}
|
|
338
|
+
function inputsFromBufferImpl() {
|
|
339
|
+
let inputVars = ''
|
|
340
|
+
if (spec.inputVars && spec.inputVars.length > 0) {
|
|
341
|
+
inputVars += '\n'
|
|
342
|
+
for (let i = 0; i < spec.inputVars.length; i++) {
|
|
343
|
+
const inputVar = spec.inputVars[i]
|
|
344
|
+
inputVars += ` ${inputVar} = inputData[${i}];\n`
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return inputVars
|
|
348
|
+
}
|
|
349
|
+
function headerTitle(varName) {
|
|
350
|
+
return Model.vensimName(varName).replace(/"/g, '\\"')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
generate: generate
|
|
355
|
+
}
|
|
356
|
+
}
|