@sdeverywhere/compile 0.7.1 → 0.7.3
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 +2 -2
- package/src/_shared/helpers.js +30 -6
- package/src/generate/code-gen.js +50 -0
- package/src/generate/equation-gen.js +18 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/compile",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"antlr4-vensim": "0.6.0",
|
|
14
14
|
"bufx": "^1.0.5",
|
|
15
15
|
"byline": "^5.0.0",
|
|
16
|
-
"csv-parse": "^
|
|
16
|
+
"csv-parse": "^5.3.3",
|
|
17
17
|
"js-yaml": "^3.13.1",
|
|
18
18
|
"ramda": "^0.27.0",
|
|
19
19
|
"split-string": "^6.0.0",
|
package/src/_shared/helpers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import util from 'util'
|
|
2
2
|
import B from 'bufx'
|
|
3
|
-
import parseCsv from 'csv-parse/
|
|
3
|
+
import { parse as parseCsv } from 'csv-parse/sync'
|
|
4
4
|
import R from 'ramda'
|
|
5
5
|
import split from 'split-string'
|
|
6
6
|
import XLSX from 'xlsx'
|
|
@@ -124,9 +124,29 @@ export let listConcat = (a, x, addSpaces = false) => {
|
|
|
124
124
|
return a + (R.isEmpty(a) ? '' : `,${s}`) + x
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
// Convert a number or string into a C double constant string.
|
|
128
|
+
// A blank string is converted to zero, following Excel.
|
|
129
|
+
// A string that cannot be converted throws an exception.
|
|
127
130
|
export let cdbl = x => {
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
function throwError() {
|
|
132
|
+
throw new Error(`ERROR: cannot convert "${x}" to a number`)
|
|
133
|
+
}
|
|
134
|
+
let s = '0.0'
|
|
135
|
+
if (typeof x === 'number') {
|
|
136
|
+
s = x.toString()
|
|
137
|
+
} else if (typeof x === 'string') {
|
|
138
|
+
if (x.trim() !== '') {
|
|
139
|
+
let f = parseFloat(x)
|
|
140
|
+
if (!Number.isNaN(f)) {
|
|
141
|
+
s = f.toString()
|
|
142
|
+
} else {
|
|
143
|
+
throwError()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
throwError()
|
|
148
|
+
}
|
|
149
|
+
// Format as a C double literal with a decimal point.
|
|
130
150
|
if (!s.includes('.') && !s.toLowerCase().includes('e')) {
|
|
131
151
|
s += '.0'
|
|
132
152
|
}
|
|
@@ -205,10 +225,14 @@ export let readCsv = (pathname, delimiter = ',') => {
|
|
|
205
225
|
columns: false,
|
|
206
226
|
trim: true,
|
|
207
227
|
skip_empty_lines: true,
|
|
208
|
-
|
|
228
|
+
skip_records_with_empty_values: true
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
let data = B.read(pathname)
|
|
232
|
+
csv = parseCsv(data, CSV_PARSE_OPTS)
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(`ERROR: readCsv ${pathname} ${err.message}`)
|
|
209
235
|
}
|
|
210
|
-
let data = B.read(pathname)
|
|
211
|
-
csv = parseCsv(data, CSV_PARSE_OPTS)
|
|
212
236
|
csvData.set(pathname, csv)
|
|
213
237
|
}
|
|
214
238
|
return csv
|
package/src/generate/code-gen.js
CHANGED
|
@@ -42,6 +42,7 @@ let codeGenerator = (parseTree, opts) => {
|
|
|
42
42
|
} else if (operation === 'generateC') {
|
|
43
43
|
// Generate code for each variable in the proper order.
|
|
44
44
|
let code = emitDeclCode()
|
|
45
|
+
code += emitGetControlValuesCode()
|
|
45
46
|
code += emitInitLookupsCode()
|
|
46
47
|
code += emitInitConstantsCode()
|
|
47
48
|
code += emitInitLevelsCode()
|
|
@@ -79,6 +80,55 @@ ${dimensionMappingsSection()}
|
|
|
79
80
|
${section(Model.lookupVars())}
|
|
80
81
|
${section(Model.dataVars())}
|
|
81
82
|
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//
|
|
87
|
+
// Control value getters section
|
|
88
|
+
//
|
|
89
|
+
function emitGetControlValuesCode() {
|
|
90
|
+
function getConstTimeValue(vensimName) {
|
|
91
|
+
const v = Model.varWithName(Model.cName(vensimName))
|
|
92
|
+
if (v && v.varType === 'const') {
|
|
93
|
+
return v.modelFormula
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error(`SDE only supports ${vensimName} defined as a constant value`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getSaveperValue() {
|
|
100
|
+
const v = Model.varWithName('_saveper')
|
|
101
|
+
if (v && v.varType === 'const') {
|
|
102
|
+
return v.modelFormula
|
|
103
|
+
} else if (v && v.varType === 'aux' && v.modelFormula === 'TIME STEP') {
|
|
104
|
+
return getConstTimeValue('TIME STEP')
|
|
105
|
+
} else {
|
|
106
|
+
throw new Error(`SDE only supports SAVEPER defined as TIME STEP or a constant value`)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// For now we only allow models that have:
|
|
111
|
+
// INITIAL TIME = a constant value
|
|
112
|
+
// FINAL TIME = a constant value
|
|
113
|
+
// SAVEPER = a constant value or constant `TIME STEP`
|
|
114
|
+
// If the model does not meet these expectations, we throw an error.
|
|
115
|
+
// TODO: Loosen this up to allow for certain common constant expressions
|
|
116
|
+
const initialTimeValue = getConstTimeValue('INITIAL TIME')
|
|
117
|
+
const finalTimeValue = getConstTimeValue('FINAL TIME')
|
|
118
|
+
const saveperValue = getSaveperValue('SAVEPER')
|
|
119
|
+
|
|
120
|
+
return `
|
|
121
|
+
// Control parameter accessors
|
|
122
|
+
double getInitialTime() {
|
|
123
|
+
return ${initialTimeValue};
|
|
124
|
+
}
|
|
125
|
+
double getFinalTime() {
|
|
126
|
+
return ${finalTimeValue};
|
|
127
|
+
}
|
|
128
|
+
double getSaveper() {
|
|
129
|
+
return ${saveperValue};
|
|
130
|
+
}
|
|
131
|
+
|
|
82
132
|
`
|
|
83
133
|
}
|
|
84
134
|
|
|
@@ -383,7 +383,15 @@ export default class EquationGen extends ModelReader {
|
|
|
383
383
|
let csvPathname = path.resolve(this.modelDirname, file)
|
|
384
384
|
let data = readCsv(csvPathname, tab)
|
|
385
385
|
if (data) {
|
|
386
|
-
getCellValue = (c, r) =>
|
|
386
|
+
getCellValue = (c, r) => {
|
|
387
|
+
let value = '0.0'
|
|
388
|
+
try {
|
|
389
|
+
value = data[r] != null && data[r][c] != null ? cdbl(data[r][c]) : null
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error(`${error.message} in ${csvPathname}`)
|
|
392
|
+
}
|
|
393
|
+
return value
|
|
394
|
+
}
|
|
387
395
|
}
|
|
388
396
|
}
|
|
389
397
|
// If the data was found, convert it to a lookup.
|
|
@@ -463,7 +471,15 @@ export default class EquationGen extends ModelReader {
|
|
|
463
471
|
let csvPathname = path.resolve(this.modelDirname, file)
|
|
464
472
|
let data = readCsv(csvPathname, tab)
|
|
465
473
|
if (data) {
|
|
466
|
-
let getCellValue = (c, r) =>
|
|
474
|
+
let getCellValue = (c, r) => {
|
|
475
|
+
let value = '0.0'
|
|
476
|
+
try {
|
|
477
|
+
value = data[r] != null && data[r][c] != null ? cdbl(data[r][c]) : null
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error(`${error.message} in ${csvPathname}`)
|
|
480
|
+
}
|
|
481
|
+
return value
|
|
482
|
+
}
|
|
467
483
|
// Get C subscripts in text form for the LHS in normal order.
|
|
468
484
|
let modelLHSReader = new ModelLHSReader()
|
|
469
485
|
modelLHSReader.read(this.var.modelLHS)
|