@sdeverywhere/compile 0.7.0 → 0.7.2
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.
|
|
3
|
+
"version": "0.7.2",
|
|
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'
|
|
@@ -14,6 +14,8 @@ let nextTmpVarSeq = 1
|
|
|
14
14
|
let nextLookupVarSeq = 1
|
|
15
15
|
// next sequence number for generated fixed delay variable names
|
|
16
16
|
let nextFixedDelayVarSeq = 1
|
|
17
|
+
// next sequence number for generated depreciation variable names
|
|
18
|
+
let nextDepreciationVarSeq = 1
|
|
17
19
|
// next sequence number for generated level variable names
|
|
18
20
|
let nextLevelVarSeq = 1
|
|
19
21
|
// next sequence number for generated aux variable names
|
|
@@ -73,6 +75,10 @@ export let newFixedDelayVarName = () => {
|
|
|
73
75
|
// Return a unique fixed delay variable name
|
|
74
76
|
return `_fixed_delay${nextFixedDelayVarSeq++}`
|
|
75
77
|
}
|
|
78
|
+
export let newDepreciationVarName = () => {
|
|
79
|
+
// Return a unique depreciation variable name
|
|
80
|
+
return `_depreciation${nextDepreciationVarSeq++}`
|
|
81
|
+
}
|
|
76
82
|
export let newLevelVarName = (basename = null, levelNumber = 0) => {
|
|
77
83
|
// Return a unique level variable name.
|
|
78
84
|
let levelName = basename || nextLevelVarSeq++
|
|
@@ -118,9 +124,29 @@ export let listConcat = (a, x, addSpaces = false) => {
|
|
|
118
124
|
return a + (R.isEmpty(a) ? '' : `,${s}`) + x
|
|
119
125
|
}
|
|
120
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.
|
|
121
130
|
export let cdbl = x => {
|
|
122
|
-
|
|
123
|
-
|
|
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.
|
|
124
150
|
if (!s.includes('.') && !s.toLowerCase().includes('e')) {
|
|
125
151
|
s += '.0'
|
|
126
152
|
}
|
|
@@ -201,8 +227,12 @@ export let readCsv = (pathname, delimiter = ',') => {
|
|
|
201
227
|
skip_empty_lines: true,
|
|
202
228
|
skip_lines_with_empty_values: true
|
|
203
229
|
}
|
|
204
|
-
|
|
205
|
-
|
|
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}`)
|
|
235
|
+
}
|
|
206
236
|
csvData.set(pathname, csv)
|
|
207
237
|
}
|
|
208
238
|
return csv
|
package/src/generate/code-gen.js
CHANGED
|
@@ -217,6 +217,7 @@ ${postStep}
|
|
|
217
217
|
function declSection() {
|
|
218
218
|
// Emit a declaration for each variable in the model.
|
|
219
219
|
let fixedDelayDecls = ''
|
|
220
|
+
let depreciationDecls = ''
|
|
220
221
|
let decl = v => {
|
|
221
222
|
// Build a C array declaration for the variable v.
|
|
222
223
|
// This uses the subscript family for each dimension, which may overallocate
|
|
@@ -229,6 +230,12 @@ ${postStep}
|
|
|
229
230
|
family => `[${sub(family).size}]`,
|
|
230
231
|
families
|
|
231
232
|
).join('')};`
|
|
233
|
+
} else if (v.isDepreciation()) {
|
|
234
|
+
// Add the associated Depreciation var decl.
|
|
235
|
+
depreciationDecls += `\nDepreciation* ${v.depreciationVarName}${R.map(
|
|
236
|
+
family => `[${sub(family).size}]`,
|
|
237
|
+
families
|
|
238
|
+
).join('')};`
|
|
232
239
|
}
|
|
233
240
|
return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('')
|
|
234
241
|
}
|
|
@@ -239,7 +246,7 @@ ${postStep}
|
|
|
239
246
|
asort,
|
|
240
247
|
lines
|
|
241
248
|
)
|
|
242
|
-
return decls(Model.allVars()) + fixedDelayDecls
|
|
249
|
+
return decls(Model.allVars()) + fixedDelayDecls + depreciationDecls
|
|
243
250
|
}
|
|
244
251
|
function internalVarsSection() {
|
|
245
252
|
// Declare internal variables to run the model.
|
|
@@ -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)
|
|
@@ -810,7 +826,13 @@ export default class EquationGen extends ModelReader {
|
|
|
810
826
|
let exprs = ctx.expr()
|
|
811
827
|
let fn = this.currentFunctionName()
|
|
812
828
|
// Split level functions into init and eval expressions.
|
|
813
|
-
if (
|
|
829
|
+
if (
|
|
830
|
+
fn === '_INTEG' ||
|
|
831
|
+
fn === '_SAMPLE_IF_TRUE' ||
|
|
832
|
+
fn === '_ACTIVE_INITIAL' ||
|
|
833
|
+
fn === '_DELAY_FIXED' ||
|
|
834
|
+
fn === '_DEPRECIATE_STRAIGHTLINE'
|
|
835
|
+
) {
|
|
814
836
|
if (this.mode.startsWith('init')) {
|
|
815
837
|
// Get the index of the argument holding the initial value.
|
|
816
838
|
let i = 0
|
|
@@ -818,10 +840,13 @@ export default class EquationGen extends ModelReader {
|
|
|
818
840
|
i = 1
|
|
819
841
|
} else if (fn === '_SAMPLE_IF_TRUE' || fn === '_DELAY_FIXED') {
|
|
820
842
|
i = 2
|
|
843
|
+
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
844
|
+
i = 3
|
|
821
845
|
}
|
|
822
846
|
this.setArgIndex(i)
|
|
823
847
|
exprs[i].accept(this)
|
|
824
|
-
// For DELAY FIXED, also initialize the support struct
|
|
848
|
+
// For DELAY FIXED and DEPRECIATE STRAIGHTLINE, also initialize the support struct
|
|
849
|
+
// out of band, as they are not Vensim vars.
|
|
825
850
|
if (fn === '_DELAY_FIXED') {
|
|
826
851
|
let fixedDelay = `${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)}`
|
|
827
852
|
this.emit(`;\n ${fixedDelay} = __new_fixed_delay(${fixedDelay}, `)
|
|
@@ -831,6 +856,15 @@ export default class EquationGen extends ModelReader {
|
|
|
831
856
|
this.setArgIndex(2)
|
|
832
857
|
exprs[2].accept(this)
|
|
833
858
|
this.emit(')')
|
|
859
|
+
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
860
|
+
let depreciation = `${this.var.depreciationVarName}${this.lhsSubscriptGen(this.var.subscripts)}`
|
|
861
|
+
this.emit(`;\n ${depreciation} = __new_depreciation(${depreciation}, `)
|
|
862
|
+
this.setArgIndex(1)
|
|
863
|
+
exprs[1].accept(this)
|
|
864
|
+
this.emit(', ')
|
|
865
|
+
this.setArgIndex(2)
|
|
866
|
+
exprs[3].accept(this)
|
|
867
|
+
this.emit(')')
|
|
834
868
|
}
|
|
835
869
|
} else {
|
|
836
870
|
// We are in eval mode, not init mode.
|
|
@@ -844,6 +878,12 @@ export default class EquationGen extends ModelReader {
|
|
|
844
878
|
exprs[0].accept(this)
|
|
845
879
|
this.emit(', ')
|
|
846
880
|
this.emit(`${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)}`)
|
|
881
|
+
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
882
|
+
// For DEPRECIATE STRAIGHTLINE, emit the first arg followed by the Depreciation support var.
|
|
883
|
+
this.setArgIndex(0)
|
|
884
|
+
exprs[0].accept(this)
|
|
885
|
+
this.emit(', ')
|
|
886
|
+
this.emit(`${this.var.depreciationVarName}${this.lhsSubscriptGen(this.var.subscripts)}`)
|
|
847
887
|
} else {
|
|
848
888
|
// Emit the variable LHS as the first arg at eval time, giving the current value for the level.
|
|
849
889
|
this.emit(this.lhs)
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
newLevelVarName,
|
|
17
17
|
newLookupVarName,
|
|
18
18
|
newFixedDelayVarName,
|
|
19
|
+
newDepreciationVarName,
|
|
19
20
|
cartesianProductOf
|
|
20
21
|
} from '../_shared/helpers.js'
|
|
21
22
|
import {
|
|
@@ -109,6 +110,10 @@ export default class EquationReader extends ModelReader {
|
|
|
109
110
|
this.var.varType = 'data'
|
|
110
111
|
} else if (fn === '_GET_DIRECT_CONSTANTS') {
|
|
111
112
|
this.var.varType = 'const'
|
|
113
|
+
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
114
|
+
this.var.hasInitValue = true
|
|
115
|
+
this.var.varSubtype = 'depreciation'
|
|
116
|
+
this.var.depreciationVarName = canonicalName(newDepreciationVarName())
|
|
112
117
|
}
|
|
113
118
|
super.visitCall(ctx)
|
|
114
119
|
this.callStack.pop()
|
|
@@ -249,6 +254,10 @@ export default class EquationReader extends ModelReader {
|
|
|
249
254
|
this.addReferencesToList(this.var.initReferences)
|
|
250
255
|
} else if (this.argIndexForFunctionName('_DELAY_FIXED') === 2) {
|
|
251
256
|
this.addReferencesToList(this.var.initReferences)
|
|
257
|
+
} else if (this.argIndexForFunctionName('_DEPRECIATE_STRAIGHTLINE') === 1) {
|
|
258
|
+
this.addReferencesToList(this.var.initReferences)
|
|
259
|
+
} else if (this.argIndexForFunctionName('_DEPRECIATE_STRAIGHTLINE') === 2) {
|
|
260
|
+
this.addReferencesToList(this.var.initReferences)
|
|
252
261
|
} else if (this.argIndexForFunctionName('_ACTIVE_INITIAL') === 1) {
|
|
253
262
|
this.addReferencesToList(this.var.initReferences)
|
|
254
263
|
} else if (this.argIndexForFunctionName('_SAMPLE_IF_TRUE') === 2) {
|
package/src/model/variable.js
CHANGED
|
@@ -45,6 +45,8 @@ export default class Variable {
|
|
|
45
45
|
this.delayTimeVarName = ''
|
|
46
46
|
// DELAY FIXED calls generate a FixedDelay support var.
|
|
47
47
|
this.fixedDelayVarName = ''
|
|
48
|
+
// DEPRECIATE STRAIGHTLINE calls generate a Depreciation support var.
|
|
49
|
+
this.depreciationVarName = ''
|
|
48
50
|
// Variables generated by special expansions are not included in output.
|
|
49
51
|
this.includeInOutput = true
|
|
50
52
|
}
|
|
@@ -99,6 +101,9 @@ export default class Variable {
|
|
|
99
101
|
isFixedDelay() {
|
|
100
102
|
return this.varSubtype === 'fixedDelay'
|
|
101
103
|
}
|
|
104
|
+
isDepreciation() {
|
|
105
|
+
return this.varSubtype === 'depreciation'
|
|
106
|
+
}
|
|
102
107
|
isInitial() {
|
|
103
108
|
return this.varType === 'initial'
|
|
104
109
|
}
|