@sdeverywhere/compile 0.7.16 → 0.7.18
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 +1 -3
- package/src/generate/expand-var-names.js +0 -10
- package/src/generate/{code-gen.js → gen-code-c.js} +192 -103
- package/src/generate/gen-code-js.js +616 -0
- package/src/generate/gen-code.js +36 -0
- package/src/generate/gen-equation.js +16 -6
- package/src/generate/gen-expr.js +205 -48
- package/src/generate/gen-lookup-from-direct.js +14 -6
- package/src/generate/gen-lookup-from-external.js +20 -5
- package/src/generate/gen-lookup-from-points.js +28 -6
- package/src/index.js +38 -3
- package/src/model/model.js +94 -151
- package/src/model/read-equation-fn-game.js +50 -0
- package/src/model/read-equations.js +9 -10
- package/src/model/read-variables.js +2 -2
- package/src/model/variable.js +6 -21
- package/src/parse-and-generate.js +22 -19
- package/src/preprocess/preprocessor.js +2 -2
- package/src/generate/equation-gen.js +0 -1268
- package/src/generate/model-lhs-reader.js +0 -88
- package/src/model/equation-reader.js +0 -723
- package/src/model/expr-reader.js +0 -202
- package/src/model/subscript-range-reader.js +0 -143
- package/src/model/var-name-reader.js +0 -40
- package/src/model/variable-reader.js +0 -172
- package/src/parse/model-reader.js +0 -141
- package/src/parse/parser.js +0 -36
|
@@ -1,1268 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { ModelLexer, ModelParser } from 'antlr4-vensim'
|
|
3
|
-
import * as R from 'ramda'
|
|
4
|
-
import XLSX from 'xlsx'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
canonicalName,
|
|
8
|
-
cartesianProductOf,
|
|
9
|
-
cdbl,
|
|
10
|
-
cFunctionName,
|
|
11
|
-
isArrayFunction,
|
|
12
|
-
isDelayFunction,
|
|
13
|
-
isSmoothFunction,
|
|
14
|
-
isTrendFunction,
|
|
15
|
-
isNpvFunction,
|
|
16
|
-
listConcat,
|
|
17
|
-
newTmpVarName,
|
|
18
|
-
permutationsOf,
|
|
19
|
-
readCsv,
|
|
20
|
-
readXlsx,
|
|
21
|
-
strToConst,
|
|
22
|
-
vlog
|
|
23
|
-
} from '../_shared/helpers.js'
|
|
24
|
-
import {
|
|
25
|
-
dimensionNames,
|
|
26
|
-
extractMarkedDims,
|
|
27
|
-
hasMapping,
|
|
28
|
-
isDimension,
|
|
29
|
-
isIndex,
|
|
30
|
-
isTrivialDimension,
|
|
31
|
-
indexInSepDim,
|
|
32
|
-
normalizeSubscripts,
|
|
33
|
-
separatedVariableIndex,
|
|
34
|
-
sub
|
|
35
|
-
} from '../_shared/subscript.js'
|
|
36
|
-
import ModelReader from '../parse/model-reader.js'
|
|
37
|
-
import Model from '../model/model.js'
|
|
38
|
-
|
|
39
|
-
import LoopIndexVars from './loop-index-vars.js'
|
|
40
|
-
import ModelLHSReader from './model-lhs-reader.js'
|
|
41
|
-
|
|
42
|
-
export default class EquationGen extends ModelReader {
|
|
43
|
-
constructor(variable, extData, directData, mode, modelDirname) {
|
|
44
|
-
super()
|
|
45
|
-
// the variable we are generating code for
|
|
46
|
-
this.var = variable
|
|
47
|
-
// external data map from DAT files
|
|
48
|
-
this.extData = extData
|
|
49
|
-
// direct data workbooks from Excel files
|
|
50
|
-
this.directData = directData
|
|
51
|
-
// set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated
|
|
52
|
-
this.mode = mode
|
|
53
|
-
// The model directory is required when reading data files for GET DIRECT DATA.
|
|
54
|
-
this.modelDirname = modelDirname
|
|
55
|
-
// Maps of LHS subscript families to loop index vars for lookup on the RHS
|
|
56
|
-
this.loopIndexVars = new LoopIndexVars(['i', 'j', 'k', 'l', 'm'])
|
|
57
|
-
this.arrayIndexVars = new LoopIndexVars(['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r'])
|
|
58
|
-
// The LHS for array variables includes subscripts in normal form.
|
|
59
|
-
this.lhs = this.var.varName + this.lhsSubscriptGen(this.var.subscripts)
|
|
60
|
-
// formula expression channel
|
|
61
|
-
this.exprCode = ''
|
|
62
|
-
// comments channel
|
|
63
|
-
this.comments = []
|
|
64
|
-
// temporary variable channel
|
|
65
|
-
this.tmpVarCode = []
|
|
66
|
-
// subscript loop opening channel
|
|
67
|
-
this.subscriptLoopOpeningCode = []
|
|
68
|
-
// subscript loop closing channel
|
|
69
|
-
this.subscriptLoopClosingCode = []
|
|
70
|
-
// the name of the current array function (might differ from `currentFunctionName`
|
|
71
|
-
// in the case where an expression is passed to an array function such as `SUM`)
|
|
72
|
-
this.currentArrayFunctionName = ''
|
|
73
|
-
// array function code buffer
|
|
74
|
-
this.arrayFunctionCode = ''
|
|
75
|
-
// the marked dimensions for an array function
|
|
76
|
-
this.markedDims = []
|
|
77
|
-
// stack of var names inside an expr
|
|
78
|
-
this.varNames = []
|
|
79
|
-
// components extracted from arguments to VECTOR ELM MAP
|
|
80
|
-
this.vemVarName = ''
|
|
81
|
-
this.vemSubscripts = []
|
|
82
|
-
this.vemIndexDim = ''
|
|
83
|
-
this.vemIndexBase = 0
|
|
84
|
-
this.vemOffset = ''
|
|
85
|
-
// components extracted from arguments to VECTOR SORT ORDER
|
|
86
|
-
this.vsoVarName = ''
|
|
87
|
-
this.vsoOrder = ''
|
|
88
|
-
this.vsoTmpName = ''
|
|
89
|
-
this.vsoTmpDimName = ''
|
|
90
|
-
// components extracted from arguments to VECTOR SELECT
|
|
91
|
-
this.vsSelectionArray = ''
|
|
92
|
-
this.vsNullValue = ''
|
|
93
|
-
this.vsAction = 0
|
|
94
|
-
this.vsError = ''
|
|
95
|
-
// components extracted from arguments to ALLOCATE AVAILABLE
|
|
96
|
-
this.aaRequestArray = ''
|
|
97
|
-
this.aaPriorityArray = ''
|
|
98
|
-
this.aaAvailableResource = ''
|
|
99
|
-
this.aaTmpName = ''
|
|
100
|
-
this.aaTmpDimName = ''
|
|
101
|
-
}
|
|
102
|
-
generate() {
|
|
103
|
-
// Generate code for the variable in either init or eval mode.
|
|
104
|
-
if (this.var.isData()) {
|
|
105
|
-
// If the data var was converted from a const, it will have lookup points.
|
|
106
|
-
// Otherwise, read a data file to get lookup data.
|
|
107
|
-
if (R.isEmpty(this.var.points)) {
|
|
108
|
-
if (this.var.directDataArgs) {
|
|
109
|
-
return this.generateDirectDataInit()
|
|
110
|
-
} else {
|
|
111
|
-
return this.generateExternalDataInit()
|
|
112
|
-
}
|
|
113
|
-
} else if (this.mode === 'decl') {
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (this.var.isLookup()) {
|
|
118
|
-
return this.generateLookup()
|
|
119
|
-
}
|
|
120
|
-
// Show the model var as a comment for reference.
|
|
121
|
-
this.comments.push(` // ${this.var.modelLHS} = ${this.var.modelFormula.replace(/\n/g, '')}`)
|
|
122
|
-
// Emit direct constants individually without separating them first.
|
|
123
|
-
if (this.var.directConstArgs) {
|
|
124
|
-
return this.generateDirectConstInit()
|
|
125
|
-
}
|
|
126
|
-
// Initialize array variables with dimensions in a loop for each dimension.
|
|
127
|
-
let dimNames = dimensionNames(this.var.subscripts)
|
|
128
|
-
// Turn each dimension name into a loop with a loop index variable.
|
|
129
|
-
// If the variable has no subscripts, nothing will be emitted here.
|
|
130
|
-
this.subscriptLoopOpeningCode = R.concat(
|
|
131
|
-
this.subscriptLoopOpeningCode,
|
|
132
|
-
R.map(dimName => {
|
|
133
|
-
let i = this.loopIndexVars.index(dimName)
|
|
134
|
-
return ` for (size_t ${i} = 0; ${i} < ${sub(dimName).size}; ${i}++) {`
|
|
135
|
-
}, dimNames)
|
|
136
|
-
)
|
|
137
|
-
// Walk the parse tree to generate code into all channels.
|
|
138
|
-
// Use this to examine code generation for a particular variable.
|
|
139
|
-
// if (this.var.refId === '') {
|
|
140
|
-
// debugger
|
|
141
|
-
// }
|
|
142
|
-
this.visitEquation(this.var.eqnCtx)
|
|
143
|
-
// Either emit constant list code or a regular var assignment.
|
|
144
|
-
let formula = ` ${this.lhs} = ${this.exprCode};`
|
|
145
|
-
// Close the assignment loops.
|
|
146
|
-
this.subscriptLoopClosingCode = R.concat(
|
|
147
|
-
this.subscriptLoopClosingCode,
|
|
148
|
-
R.map(() => ` }`, dimNames)
|
|
149
|
-
)
|
|
150
|
-
// Assemble code from each channel into final var code output.
|
|
151
|
-
return this.comments.concat(this.subscriptLoopOpeningCode, this.tmpVarCode, formula, this.subscriptLoopClosingCode)
|
|
152
|
-
}
|
|
153
|
-
//
|
|
154
|
-
// Helpers
|
|
155
|
-
//
|
|
156
|
-
currentVarName() {
|
|
157
|
-
let n = this.varNames.length
|
|
158
|
-
return n > 0 ? this.varNames[n - 1] : undefined
|
|
159
|
-
}
|
|
160
|
-
lookupName() {
|
|
161
|
-
// Convert a call name into a lookup name.
|
|
162
|
-
return canonicalName(this.currentFunctionName()).slice(1)
|
|
163
|
-
}
|
|
164
|
-
emit(text) {
|
|
165
|
-
if (this.currentArrayFunctionName) {
|
|
166
|
-
// Emit code to the array function code buffer if we are in an array function.
|
|
167
|
-
this.arrayFunctionCode += text
|
|
168
|
-
} else if (this.argIndexForFunctionName('_VECTOR_ELM_MAP') === 1) {
|
|
169
|
-
// Emit expression code in the second argument of VECTOR ELM MAP to vemOffset.
|
|
170
|
-
this.vemOffset += text
|
|
171
|
-
} else {
|
|
172
|
-
// Otherwise emit code to the expression code channel.
|
|
173
|
-
this.exprCode += text
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
cVarOrConst(expr) {
|
|
177
|
-
// Get either a constant or a var name in C format from a parse tree expression.
|
|
178
|
-
let value = expr.getText().trim()
|
|
179
|
-
if (value === ':NA:') {
|
|
180
|
-
return '_NA_'
|
|
181
|
-
} else {
|
|
182
|
-
let v = Model.varWithName(canonicalName(value))
|
|
183
|
-
if (v) {
|
|
184
|
-
return v.varName
|
|
185
|
-
} else {
|
|
186
|
-
let d = parseFloat(value)
|
|
187
|
-
if (Number.isNaN(d)) {
|
|
188
|
-
d = 0
|
|
189
|
-
}
|
|
190
|
-
return cdbl(d)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
constValue(c) {
|
|
195
|
-
// Get a numeric value from a constant var name in model form.
|
|
196
|
-
// Return 0 if the value is not a numeric string or const variable.
|
|
197
|
-
let value = parseFloat(c)
|
|
198
|
-
if (!Number.isNaN(value)) {
|
|
199
|
-
return value
|
|
200
|
-
}
|
|
201
|
-
// Look up the value as a symbol name and return the const value.
|
|
202
|
-
value = 0
|
|
203
|
-
let v = Model.varWithName(canonicalName(c))
|
|
204
|
-
if (v && v.isConst()) {
|
|
205
|
-
value = parseFloat(v.modelFormula)
|
|
206
|
-
if (Number.isNaN(value)) {
|
|
207
|
-
value = 0
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return value
|
|
211
|
-
}
|
|
212
|
-
handleExcelWorkbook(fileOrTag, workbook, tab, dataKind, dataSource) {
|
|
213
|
-
// Return a `getCellValue` function for the given Excel workbook parsed from an XLS[X] file.
|
|
214
|
-
if (workbook) {
|
|
215
|
-
let sheet = workbook.Sheets[tab]
|
|
216
|
-
if (sheet) {
|
|
217
|
-
return (c, r) => {
|
|
218
|
-
let cell = sheet[XLSX.utils.encode_cell({ c, r })]
|
|
219
|
-
return cell != null ? cdbl(cell.v) : null
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
throw new Error(`Direct ${dataKind} worksheet ${tab} in ${dataSource} ${fileOrTag} not found`)
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
throw new Error(`Direct ${dataKind} workbook ${dataSource} ${fileOrTag} not found`)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
handleCsvFile(file, dataPathname, tab, dataKind) {
|
|
229
|
-
// Return a `getCellValue` function for the given CSV file.
|
|
230
|
-
let data = readCsv(dataPathname, tab)
|
|
231
|
-
if (data) {
|
|
232
|
-
return (c, r) => {
|
|
233
|
-
let value = '0.0'
|
|
234
|
-
try {
|
|
235
|
-
value = data[r] != null && data[r][c] != null ? cdbl(data[r][c]) : null
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.error(`${error.message} in ${dataPathname}`)
|
|
238
|
-
}
|
|
239
|
-
return value
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
throw new Error(`Direct ${dataKind} file ${file} could not be read`)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
handleExcelOrCsvFile(fileOrTag, tab, dataKind) {
|
|
246
|
-
// Return a `getCellValue` function that reads the CSV or XLS[X] content.
|
|
247
|
-
if (fileOrTag.startsWith('?')) {
|
|
248
|
-
// The file is a tag for an Excel file with data in the directData map.
|
|
249
|
-
let workbook = this.directData.get(fileOrTag)
|
|
250
|
-
return this.handleExcelWorkbook(fileOrTag, workbook, tab, dataKind, 'tagged')
|
|
251
|
-
} else {
|
|
252
|
-
// The file is a CSV or XLS[X] pathname. Read it now.
|
|
253
|
-
let dataPathname = path.resolve(this.modelDirname, fileOrTag)
|
|
254
|
-
if (dataPathname.toLowerCase().endsWith('csv')) {
|
|
255
|
-
return this.handleCsvFile(fileOrTag, dataPathname, tab, dataKind)
|
|
256
|
-
} else {
|
|
257
|
-
let workbook = readXlsx(dataPathname)
|
|
258
|
-
return this.handleExcelWorkbook(fileOrTag, workbook, tab, dataKind, 'file')
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
lookupDataNameGen(subscripts) {
|
|
263
|
-
// Construct a name for the static data array associated with a lookup variable.
|
|
264
|
-
return R.map(subscript => {
|
|
265
|
-
if (isDimension(subscript)) {
|
|
266
|
-
let i = this.loopIndexVars.index(subscript)
|
|
267
|
-
if (isTrivialDimension(subscript)) {
|
|
268
|
-
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
269
|
-
return `_${i}_`
|
|
270
|
-
} else {
|
|
271
|
-
return `_${subscript}_${i}_`
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
return `_${sub(subscript).value}_`
|
|
275
|
-
}
|
|
276
|
-
}, subscripts).join('')
|
|
277
|
-
}
|
|
278
|
-
lhsSubscriptGen(subscripts) {
|
|
279
|
-
// Construct C array subscripts from subscript names in the variable's normal order.
|
|
280
|
-
return R.map(subscript => {
|
|
281
|
-
if (isDimension(subscript)) {
|
|
282
|
-
let i = this.loopIndexVars.index(subscript)
|
|
283
|
-
if (isTrivialDimension(subscript)) {
|
|
284
|
-
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
285
|
-
return `[${i}]`
|
|
286
|
-
} else {
|
|
287
|
-
return `[${subscript}[${i}]]`
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
return `[${sub(subscript).value}]`
|
|
291
|
-
}
|
|
292
|
-
}, subscripts).join('')
|
|
293
|
-
}
|
|
294
|
-
rhsSubscriptGen(subscripts) {
|
|
295
|
-
// Normalize RHS subscripts.
|
|
296
|
-
try {
|
|
297
|
-
subscripts = normalizeSubscripts(subscripts)
|
|
298
|
-
} catch (e) {
|
|
299
|
-
console.error('ERROR: normalizeSubscripts failed in rhsSubscriptGen')
|
|
300
|
-
vlog('this.var.refId', this.var.refId)
|
|
301
|
-
vlog('this.currentVarName()', this.currentVarName())
|
|
302
|
-
vlog('subscripts', subscripts)
|
|
303
|
-
throw e
|
|
304
|
-
}
|
|
305
|
-
// Get the loop index var name source.
|
|
306
|
-
let cSubscripts = R.map(rhsSub => {
|
|
307
|
-
if (isIndex(rhsSub)) {
|
|
308
|
-
// Return the index number for an index subscript.
|
|
309
|
-
return `[${sub(rhsSub).value}]`
|
|
310
|
-
} else {
|
|
311
|
-
// The subscript is a dimension.
|
|
312
|
-
// Get the loop index variable, matching the previously emitted for loop variable.
|
|
313
|
-
let i
|
|
314
|
-
if (this.markedDims.includes(rhsSub)) {
|
|
315
|
-
i = this.arrayIndexVars.index(rhsSub)
|
|
316
|
-
} else {
|
|
317
|
-
// Use the single index name for a separated variable if it exists.
|
|
318
|
-
let separatedIndexName = separatedVariableIndex(rhsSub, this.var, subscripts)
|
|
319
|
-
if (separatedIndexName) {
|
|
320
|
-
return `[${sub(separatedIndexName).value}]`
|
|
321
|
-
}
|
|
322
|
-
// See if we need to apply a mapping because the RHS dim is not found on the LHS.
|
|
323
|
-
let found = this.var.subscripts.findIndex(lhsSub => sub(lhsSub).family === sub(rhsSub).family)
|
|
324
|
-
if (found < 0) {
|
|
325
|
-
// Find the mapping from the RHS subscript to a LHS subscript.
|
|
326
|
-
for (let lhsSub of this.var.subscripts) {
|
|
327
|
-
if (hasMapping(rhsSub, lhsSub)) {
|
|
328
|
-
// console.error(`${this.var.refId} hasMapping ${rhsSub} → ${lhsSub}`);
|
|
329
|
-
i = this.loopIndexVars.index(lhsSub)
|
|
330
|
-
return `[__map${rhsSub}${lhsSub}[${i}]]`
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
// There is no mapping, so use the loop index for this dim family on the LHS.
|
|
335
|
-
i = this.loopIndexVars.index(rhsSub)
|
|
336
|
-
}
|
|
337
|
-
// Return the dimension and loop index for a dimension subscript.
|
|
338
|
-
if (isTrivialDimension(rhsSub)) {
|
|
339
|
-
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
340
|
-
return `[${i}]`
|
|
341
|
-
} else {
|
|
342
|
-
return `[${rhsSub}[${i}]]`
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}, subscripts).join('')
|
|
346
|
-
return cSubscripts
|
|
347
|
-
}
|
|
348
|
-
vemSubscriptGen() {
|
|
349
|
-
// VECTOR ELM MAP replaces one subscript with a calculated vemOffset from a base index.
|
|
350
|
-
let subscripts = normalizeSubscripts(this.vemSubscripts)
|
|
351
|
-
let cSubscripts = R.map(rhsSub => {
|
|
352
|
-
if (isIndex(rhsSub)) {
|
|
353
|
-
// Emit the index vemOffset from VECTOR ELM MAP for the index subscript.
|
|
354
|
-
return `[${this.vemIndexDim}[(size_t)(${this.vemIndexBase} + ${this.vemOffset})]]`
|
|
355
|
-
} else {
|
|
356
|
-
let i = this.loopIndexVars.index(rhsSub)
|
|
357
|
-
return `[${rhsSub}[${i}]]`
|
|
358
|
-
}
|
|
359
|
-
}, subscripts).join('')
|
|
360
|
-
return cSubscripts
|
|
361
|
-
}
|
|
362
|
-
vsoSubscriptGen(subscripts) {
|
|
363
|
-
// _VECTOR_SORT_ORDER will iterate over the last subscript in its first arg.
|
|
364
|
-
let i = this.loopIndexVars.index(subscripts[0])
|
|
365
|
-
if (subscripts.length > 1) {
|
|
366
|
-
this.vsoVarName += `[${subscripts[0]}[${i}]]`
|
|
367
|
-
i = this.loopIndexVars.index(subscripts[1])
|
|
368
|
-
this.vsoTmpDimName = subscripts[1]
|
|
369
|
-
} else {
|
|
370
|
-
this.vsoTmpDimName = subscripts[0]
|
|
371
|
-
}
|
|
372
|
-
// Emit the tmp var subscript just after emitting the tmp var elsewhere.
|
|
373
|
-
this.emit(`[${this.vsoTmpDimName}[${i}]]`)
|
|
374
|
-
}
|
|
375
|
-
aaSubscriptGen(subscripts) {
|
|
376
|
-
// _ALLOCATE_AVAILABLE will iterate over the subscript in its first arg.
|
|
377
|
-
let i = this.loopIndexVars.index(subscripts[0])
|
|
378
|
-
this.aaTmpDimName = subscripts[0]
|
|
379
|
-
// Emit the tmp var subscript just after emitting the tmp var elsewhere.
|
|
380
|
-
this.emit(`[${this.aaTmpDimName}[${i}]]`)
|
|
381
|
-
}
|
|
382
|
-
functionIsLookup() {
|
|
383
|
-
// See if the function name in the current call is actually a lookup.
|
|
384
|
-
// console.error(`isLookup ${this.lookupName()}`);
|
|
385
|
-
let v = Model.varWithName(this.lookupName())
|
|
386
|
-
return v && v.isLookup()
|
|
387
|
-
}
|
|
388
|
-
generateLookup() {
|
|
389
|
-
// Construct the name of the data array, which is based on the associated lookup var name,
|
|
390
|
-
// with any subscripts tacked on the end.
|
|
391
|
-
const dataName = this.var.varName + '_data_' + this.lookupDataNameGen(this.var.subscripts)
|
|
392
|
-
if (this.mode === 'decl') {
|
|
393
|
-
// In decl mode, declare a static data array that will be used to create the associated `Lookup`
|
|
394
|
-
// at init time. Using static arrays is better for code size, helps us avoid creating a copy of
|
|
395
|
-
// the data in memory, and seems to perform much better when compiled to wasm when compared to the
|
|
396
|
-
// previous approach that used varargs + copying, especially on constrained (e.g. iOS) devices.
|
|
397
|
-
let data = R.reduce((a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true), '', this.var.points)
|
|
398
|
-
return [`double ${dataName}[${this.var.points.length * 2}] = { ${data} };`]
|
|
399
|
-
} else if (this.mode === 'init-lookups') {
|
|
400
|
-
// In init mode, create the `Lookup`, passing in a pointer to the static data array declared earlier.
|
|
401
|
-
// TODO: Make use of the lookup range
|
|
402
|
-
if (this.var.points.length < 1) {
|
|
403
|
-
throw new Error(`ERROR: lookup size = ${this.var.points.length} in ${this.lhs}`)
|
|
404
|
-
}
|
|
405
|
-
return [` ${this.lhs} = __new_lookup(${this.var.points.length}, /*copy=*/false, ${dataName});`]
|
|
406
|
-
} else {
|
|
407
|
-
return []
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
generateDirectDataInit() {
|
|
411
|
-
// If direct data exists for this variable, copy it from the workbook into one or more lookups.
|
|
412
|
-
let result = []
|
|
413
|
-
if (this.mode === 'init-lookups') {
|
|
414
|
-
let { file, tab, timeRowOrCol, startCell } = this.var.directDataArgs
|
|
415
|
-
|
|
416
|
-
// Create a function that reads the CSV or XLS[X] content
|
|
417
|
-
let getCellValue = this.handleExcelOrCsvFile(file, tab, 'data')
|
|
418
|
-
|
|
419
|
-
// If the data was found, convert it to a lookup.
|
|
420
|
-
if (getCellValue) {
|
|
421
|
-
let indexNum = 0
|
|
422
|
-
if (!R.isEmpty(this.var.separationDims)) {
|
|
423
|
-
// Generate a lookup for a separated index in the variable's dimension.
|
|
424
|
-
if (this.var.separationDims.length > 1) {
|
|
425
|
-
console.error(`WARNING: direct data variable ${this.var.varName} separated on more than one dimension`)
|
|
426
|
-
}
|
|
427
|
-
let dimName = this.var.separationDims[0]
|
|
428
|
-
for (let subscript of this.var.subscripts) {
|
|
429
|
-
if (sub(subscript).family === dimName) {
|
|
430
|
-
// Use the index value in the subscript family when that is the separation dimension.
|
|
431
|
-
indexNum = sub(subscript).value
|
|
432
|
-
break
|
|
433
|
-
}
|
|
434
|
-
if (sub(dimName).value.includes(subscript)) {
|
|
435
|
-
// Look up the index when the separation dimension is a subdimension.
|
|
436
|
-
indexNum = sub(dimName).value.indexOf(subscript)
|
|
437
|
-
break
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
result.push(this.generateDirectDataLookup(getCellValue, timeRowOrCol, startCell, indexNum))
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return result
|
|
445
|
-
}
|
|
446
|
-
generateDirectDataLookup(getCellValue, timeRowOrCol, startCell, indexNum) {
|
|
447
|
-
// Read a row or column of data as (time, value) pairs from the worksheet.
|
|
448
|
-
// The cell(c,r) function wraps data access by column and row.
|
|
449
|
-
let dataCol, dataRow, dataValue, timeCol, timeRow, timeValue, nextCell
|
|
450
|
-
let lookupData = ''
|
|
451
|
-
let lookupSize = 0
|
|
452
|
-
let dataAddress = XLSX.utils.decode_cell(startCell.toUpperCase())
|
|
453
|
-
dataCol = dataAddress.c
|
|
454
|
-
dataRow = dataAddress.r
|
|
455
|
-
if (dataCol < 0 || dataRow < 0) {
|
|
456
|
-
throw new Error(
|
|
457
|
-
`Failed to parse 'cell' argument for GET DIRECT {DATA,LOOKUPS} call for ${this.lhs}: ${startCell}`
|
|
458
|
-
)
|
|
459
|
-
}
|
|
460
|
-
if (isNaN(parseInt(timeRowOrCol))) {
|
|
461
|
-
// Time values are in a column.
|
|
462
|
-
timeCol = XLSX.utils.decode_col(timeRowOrCol.toUpperCase())
|
|
463
|
-
timeRow = dataRow
|
|
464
|
-
dataCol += indexNum
|
|
465
|
-
nextCell = () => {
|
|
466
|
-
dataRow++
|
|
467
|
-
timeRow++
|
|
468
|
-
}
|
|
469
|
-
} else {
|
|
470
|
-
// Time values are in a row.
|
|
471
|
-
timeCol = dataCol
|
|
472
|
-
timeRow = XLSX.utils.decode_row(timeRowOrCol)
|
|
473
|
-
dataRow += indexNum
|
|
474
|
-
nextCell = () => {
|
|
475
|
-
dataCol++
|
|
476
|
-
timeCol++
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
timeValue = getCellValue(timeCol, timeRow)
|
|
480
|
-
dataValue = getCellValue(dataCol, dataRow)
|
|
481
|
-
while (timeValue != null && dataValue != null) {
|
|
482
|
-
lookupData = listConcat(lookupData, `${timeValue}, ${dataValue}`, true)
|
|
483
|
-
lookupSize++
|
|
484
|
-
nextCell()
|
|
485
|
-
dataValue = getCellValue(dataCol, dataRow)
|
|
486
|
-
timeValue = getCellValue(timeCol, timeRow)
|
|
487
|
-
}
|
|
488
|
-
if (lookupSize < 1) {
|
|
489
|
-
throw new Error(`ERROR: lookup size = ${lookupSize} in ${this.lhs}`)
|
|
490
|
-
}
|
|
491
|
-
return [` ${this.lhs} = __new_lookup(${lookupSize}, /*copy=*/true, (double[]){ ${lookupData} });`]
|
|
492
|
-
}
|
|
493
|
-
generateDirectConstInit() {
|
|
494
|
-
// Map zero, one, or two subscripts on the LHS in model order to a table of numbers in a CSV file.
|
|
495
|
-
// The subscripts may be indices to pick out a subset of the data.
|
|
496
|
-
let result = this.comments
|
|
497
|
-
let { file, tab, startCell } = this.var.directConstArgs
|
|
498
|
-
|
|
499
|
-
// Create a function that reads the CSV or XLS[X] content
|
|
500
|
-
let getCellValue = this.handleExcelOrCsvFile(file, tab, 'constants')
|
|
501
|
-
if (getCellValue) {
|
|
502
|
-
// Get C subscripts in text form for the LHS in normal order.
|
|
503
|
-
let modelLHSReader = new ModelLHSReader()
|
|
504
|
-
modelLHSReader.read(this.var.modelLHS)
|
|
505
|
-
let modelDimNames = modelLHSReader.modelSubscripts.filter(s => isDimension(s))
|
|
506
|
-
// Generate offsets from the start cell in the table corresponding to LHS indices.
|
|
507
|
-
let cellOffsets = []
|
|
508
|
-
let cSubscripts = this.var.subscripts.map(s => (isDimension(s) ? sub(s).value : [s]))
|
|
509
|
-
let lhsIndexSubscripts = cartesianProductOf(cSubscripts)
|
|
510
|
-
// Find the table cell offset for each LHS index tuple.
|
|
511
|
-
for (let indexSubscripts of lhsIndexSubscripts) {
|
|
512
|
-
let entry = [null, null]
|
|
513
|
-
for (let i = 0; i < this.var.subscripts.length; i++) {
|
|
514
|
-
// LHS dimensions or indices in a separated dimension map to table cells.
|
|
515
|
-
let lhsSubscript = this.var.subscripts[i]
|
|
516
|
-
if (isDimension(lhsSubscript) || indexInSepDim(lhsSubscript, this.var)) {
|
|
517
|
-
// Consider the LHS index subscript at this position.
|
|
518
|
-
let indexSubscript = indexSubscripts[i]
|
|
519
|
-
let ind = sub(indexSubscript)
|
|
520
|
-
// Find the model subscript position corresponding to the LHS index subscript.
|
|
521
|
-
for (let iModelDim = 0; iModelDim < modelDimNames.length; iModelDim++) {
|
|
522
|
-
// Only fill an entry position once.
|
|
523
|
-
if (entry[iModelDim] === null) {
|
|
524
|
-
let modelDim = sub(modelDimNames[iModelDim])
|
|
525
|
-
if (modelDim.family === ind.family) {
|
|
526
|
-
// Set the numeric index for the model dimension in the cell offset entry.
|
|
527
|
-
// Use the position within the dimension to map subdimensions onto cell offsets.
|
|
528
|
-
let pos = modelDim.value.indexOf(indexSubscript)
|
|
529
|
-
// Vectors use a 2D cell offset that maps to columns in the first row.
|
|
530
|
-
// Tables use a 2D cell offset with the row or column matching the model dimension.
|
|
531
|
-
let entryRowOrCol = modelDimNames.length > 1 ? iModelDim : 1
|
|
532
|
-
entry[entryRowOrCol] = pos
|
|
533
|
-
break
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
// Replace unfilled entry positions with zero.
|
|
540
|
-
entry = entry.map(x => (x === null ? 0 : x))
|
|
541
|
-
// Read values by column first when the start cell ends with an asterisk.
|
|
542
|
-
// Ref: https://www.vensim.com/documentation/fn_get_direct_constants.html
|
|
543
|
-
if (startCell.endsWith('*')) {
|
|
544
|
-
entry.reverse()
|
|
545
|
-
}
|
|
546
|
-
cellOffsets.push(entry)
|
|
547
|
-
}
|
|
548
|
-
// Read tabular data into an indexed variable for each cell.
|
|
549
|
-
let numericSubscripts = lhsIndexSubscripts.map(idx => idx.map(s => sub(s).value))
|
|
550
|
-
let lhsSubscripts = numericSubscripts.map(s => s.reduce((a, v) => a.concat(`[${v}]`), ''))
|
|
551
|
-
let dataAddress = XLSX.utils.decode_cell(startCell.toUpperCase())
|
|
552
|
-
let startCol = dataAddress.c
|
|
553
|
-
let startRow = dataAddress.r
|
|
554
|
-
if (startCol < 0 || startRow < 0) {
|
|
555
|
-
throw new Error(`Failed to parse 'cell' argument for GET DIRECT CONSTANTS call for ${this.lhs}: ${startCell}`)
|
|
556
|
-
}
|
|
557
|
-
for (let i = 0; i < cellOffsets.length; i++) {
|
|
558
|
-
let rowOffset = cellOffsets[i][0] ? cellOffsets[i][0] : 0
|
|
559
|
-
let colOffset = cellOffsets[i][1] ? cellOffsets[i][1] : 0
|
|
560
|
-
let dataValue = getCellValue(startCol + colOffset, startRow + rowOffset)
|
|
561
|
-
let lhs = `${this.var.varName}${lhsSubscripts[i] || ''}`
|
|
562
|
-
result.push(` ${lhs} = ${dataValue};`)
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return result
|
|
566
|
-
}
|
|
567
|
-
generateExternalDataInit() {
|
|
568
|
-
// If there is external data for this variable, copy it from an external file to a lookup.
|
|
569
|
-
// Just like in generateLookup(), we declare static arrays to hold the data points in the first pass
|
|
570
|
-
// ("decl" mode), then initialize each `Lookup` using that data in the second pass ("init" mode).
|
|
571
|
-
const mode = this.mode
|
|
572
|
-
|
|
573
|
-
const newLookup = (name, lhs, data, subscriptIndexes) => {
|
|
574
|
-
if (!data) {
|
|
575
|
-
throw new Error(`ERROR: Data for ${name} not found in external data sources`)
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const dataName = this.var.varName + '_data_' + R.map(i => `_${i}_`, subscriptIndexes).join('')
|
|
579
|
-
if (mode === 'decl') {
|
|
580
|
-
// In decl mode, declare a static data array that will be used to create the associated `Lookup`
|
|
581
|
-
// at init time. See `generateLookup` for more details.
|
|
582
|
-
const points = R.reduce(
|
|
583
|
-
(a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true),
|
|
584
|
-
'',
|
|
585
|
-
Array.from(data.entries())
|
|
586
|
-
)
|
|
587
|
-
return `double ${dataName}[${data.size * 2}] = { ${points} };`
|
|
588
|
-
} else if (mode === 'init-lookups') {
|
|
589
|
-
// In init mode, create the `Lookup`, passing in a pointer to the static data array declared in decl mode.
|
|
590
|
-
if (data.size < 1) {
|
|
591
|
-
throw new Error(`ERROR: lookup size = ${data.size} in ${lhs}`)
|
|
592
|
-
}
|
|
593
|
-
return ` ${lhs} = __new_lookup(${data.size}, /*copy=*/false, ${dataName});`
|
|
594
|
-
} else {
|
|
595
|
-
return undefined
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// There are three common cases that we handle:
|
|
600
|
-
// - variable has no subscripts (C variable _thing = _thing from dat file)
|
|
601
|
-
// - variable has subscript(s) (C variable with index _thing[0] = _thing[_subscript] from dat file)
|
|
602
|
-
// - variable has dimension(s) (C variable in for loop, _thing[i] = _thing[_subscript_i] from dat file)
|
|
603
|
-
|
|
604
|
-
if (!this.var.subscripts || this.var.subscripts.length === 0) {
|
|
605
|
-
// No subscripts
|
|
606
|
-
const data = this.extData.get(this.var.varName)
|
|
607
|
-
return [newLookup(this.var.varName, this.lhs, data, [])]
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (this.var.subscripts.length === 1 && !isDimension(this.var.subscripts[0])) {
|
|
611
|
-
// There is exactly one subscript
|
|
612
|
-
const subscript = this.var.subscripts[0]
|
|
613
|
-
const nameInDat = `${this.var.varName}[${subscript}]`
|
|
614
|
-
const data = this.extData.get(nameInDat)
|
|
615
|
-
const subIndex = sub(subscript).value
|
|
616
|
-
return [newLookup(nameInDat, this.lhs, data, [subIndex])]
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (!R.all(s => isDimension(s), this.var.subscripts)) {
|
|
620
|
-
// We don't yet handle the case where there are more than one subscript or a mix of
|
|
621
|
-
// subscripts and dimensions
|
|
622
|
-
// TODO: Remove this restriction
|
|
623
|
-
throw new Error(`ERROR: Data variable ${this.var.varName} has >= 2 subscripts; not yet handled`)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// At this point, we know that we have one or more dimensions; compute all combinations
|
|
627
|
-
// of the dimensions that we will iterate over
|
|
628
|
-
const result = []
|
|
629
|
-
const allDims = R.map(s => sub(s).value, this.var.subscripts)
|
|
630
|
-
const dimTuples = cartesianProductOf(allDims)
|
|
631
|
-
for (const dims of dimTuples) {
|
|
632
|
-
// Note: It appears that the dat file can have the subscripts in a different order
|
|
633
|
-
// than what SDE uses when declaring the C array. If we don't find data for one
|
|
634
|
-
// order, we try the other possible permutations.
|
|
635
|
-
const dimNamePermutations = permutationsOf(dims)
|
|
636
|
-
let nameInDat, data
|
|
637
|
-
for (const dimNames of dimNamePermutations) {
|
|
638
|
-
nameInDat = `${this.var.varName}[${dimNames.join(',')}]`
|
|
639
|
-
data = this.extData.get(nameInDat)
|
|
640
|
-
if (data) {
|
|
641
|
-
break
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
if (!data) {
|
|
645
|
-
// We currently treat this as a warning, not an error, since there can sometimes be
|
|
646
|
-
// datasets that are a sparse matrix, i.e., data is not defined for certain dimensions.
|
|
647
|
-
// For these cases, the lookup will not be initialized (the Lookup pointer will remain
|
|
648
|
-
// NULL, and any calls to `LOOKUP` will return `:NA:`.
|
|
649
|
-
if (mode === 'decl') {
|
|
650
|
-
console.error(`WARNING: Data for ${nameInDat} not found in external data sources`)
|
|
651
|
-
}
|
|
652
|
-
continue
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const subscriptIndexes = R.map(dim => sub(dim).value, dims)
|
|
656
|
-
const varSubscripts = R.map(index => `[${index}]`, subscriptIndexes).join('')
|
|
657
|
-
const lhs = `${this.var.varName}${varSubscripts}`
|
|
658
|
-
const lookup = newLookup(nameInDat, lhs, data, subscriptIndexes)
|
|
659
|
-
if (lookup) {
|
|
660
|
-
result.push(lookup)
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
return result
|
|
664
|
-
}
|
|
665
|
-
//
|
|
666
|
-
// Visitor callbacks
|
|
667
|
-
//
|
|
668
|
-
visitEquation(ctx) {
|
|
669
|
-
if (this.var.isData() && !R.isEmpty(this.var.points)) {
|
|
670
|
-
if (this.mode === 'init-lookups') {
|
|
671
|
-
// If the var already has lookup data points, use those instead of reading them from a file.
|
|
672
|
-
if (this.var.points.length < 1) {
|
|
673
|
-
throw new Error(`ERROR: lookup size = ${this.var.points.length} in ${this.var.refId}`)
|
|
674
|
-
}
|
|
675
|
-
let lookupData = R.reduce((a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true), '', this.var.points)
|
|
676
|
-
this.emit(`__new_lookup(${this.var.points.length}, /*copy=*/true, (double[]){ ${lookupData} })`)
|
|
677
|
-
}
|
|
678
|
-
} else {
|
|
679
|
-
super.visitEquation(ctx)
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
visitCall(ctx) {
|
|
683
|
-
// Convert the function name from Vensim to C format and push it onto the function name stack.
|
|
684
|
-
// This maintains the name of the current function as its arguments are visited.
|
|
685
|
-
this.callStack.push({ fn: cFunctionName(ctx.Id().getText()) })
|
|
686
|
-
let fn = this.currentFunctionName()
|
|
687
|
-
// Do not emit the function calls in init mode, only the init expression.
|
|
688
|
-
// Do emit function calls inside an init expression (with call stack length > 1).
|
|
689
|
-
if (this.var.hasInitValue && this.mode.startsWith('init') && this.callStack.length <= 1) {
|
|
690
|
-
super.visitCall(ctx)
|
|
691
|
-
this.callStack.pop()
|
|
692
|
-
} else if (fn === '_ELMCOUNT') {
|
|
693
|
-
// Replace the function with the value of its argument, emitted in visitVar.
|
|
694
|
-
super.visitCall(ctx)
|
|
695
|
-
this.callStack.pop()
|
|
696
|
-
} else if (isArrayFunction(fn)) {
|
|
697
|
-
// Capture the name of this array function (e.g. `SUM`). This should be used
|
|
698
|
-
// to determine if a subscripted variable is used inside of an expression
|
|
699
|
-
// passed to an array function, e.g.:
|
|
700
|
-
// SUM ( Variable[Dim] )
|
|
701
|
-
// or
|
|
702
|
-
// SUM ( IF THEN ELSE ( Variable[Dim], ... ) )
|
|
703
|
-
// In the first example, when `Variable` is evaluated, both `currentFunctionName`
|
|
704
|
-
// and `currentArrayFunctionName` will be `SUM`. But in the second case, when
|
|
705
|
-
// `Variable` is evaluated, `currentFunctionName` will be `IF THEN ELSE` but
|
|
706
|
-
// `currentArrayFunctionName` will be `SUM`. A non-empty `currentArrayFunctionName`
|
|
707
|
-
// is an indication that a loop needs to be generated.
|
|
708
|
-
this.currentArrayFunctionName = fn
|
|
709
|
-
// Generate a loop that evaluates array functions inline.
|
|
710
|
-
// Collect information and generate the argument expression into the array function code buffer.
|
|
711
|
-
super.visitCall(ctx)
|
|
712
|
-
// Start a temporary variable to hold the result of the array function.
|
|
713
|
-
let condVar
|
|
714
|
-
let initValue = '0.0'
|
|
715
|
-
if (fn === '_VECTOR_SELECT') {
|
|
716
|
-
initValue = this.vsAction === 3 ? '-DBL_MAX' : '0.0'
|
|
717
|
-
condVar = newTmpVarName()
|
|
718
|
-
this.tmpVarCode.push(` bool ${condVar} = false;`)
|
|
719
|
-
} else if (fn === '_VMIN') {
|
|
720
|
-
initValue = 'DBL_MAX'
|
|
721
|
-
} else if (fn === '_VMAX') {
|
|
722
|
-
initValue = '-DBL_MAX'
|
|
723
|
-
}
|
|
724
|
-
let tmpVar = newTmpVarName()
|
|
725
|
-
this.tmpVarCode.push(` double ${tmpVar} = ${initValue};`)
|
|
726
|
-
// Emit the array function loop opening into the tmp var channel.
|
|
727
|
-
for (let markedDim of this.markedDims) {
|
|
728
|
-
let n
|
|
729
|
-
try {
|
|
730
|
-
n = sub(markedDim).size
|
|
731
|
-
} catch (e) {
|
|
732
|
-
console.error(`ERROR: marked dimension "${markedDim}" not found in var ${this.var.refId}`)
|
|
733
|
-
throw e
|
|
734
|
-
}
|
|
735
|
-
let i = this.arrayIndexVars.index(markedDim)
|
|
736
|
-
this.tmpVarCode.push(` for (size_t ${i} = 0; ${i} < ${n}; ${i}++) {`)
|
|
737
|
-
}
|
|
738
|
-
// Emit the body of the array function loop.
|
|
739
|
-
if (fn === '_VECTOR_SELECT') {
|
|
740
|
-
this.tmpVarCode.push(` if (bool_cond(${this.vsSelectionArray})) {`)
|
|
741
|
-
}
|
|
742
|
-
if (fn === '_SUM' || (fn === '_VECTOR_SELECT' && this.vsAction === 0)) {
|
|
743
|
-
this.tmpVarCode.push(` ${tmpVar} += ${this.arrayFunctionCode};`)
|
|
744
|
-
} else if (fn === '_VMIN') {
|
|
745
|
-
this.tmpVarCode.push(` ${tmpVar} = fmin(${tmpVar}, ${this.arrayFunctionCode});`)
|
|
746
|
-
} else if (fn === '_VMAX' || (fn === '_VECTOR_SELECT' && this.vsAction === 3)) {
|
|
747
|
-
this.tmpVarCode.push(` ${tmpVar} = fmax(${tmpVar}, ${this.arrayFunctionCode});`)
|
|
748
|
-
}
|
|
749
|
-
if (fn === '_VECTOR_SELECT') {
|
|
750
|
-
this.tmpVarCode.push(` ${condVar} = true;`)
|
|
751
|
-
this.tmpVarCode.push(' }')
|
|
752
|
-
}
|
|
753
|
-
// Close the array function loops.
|
|
754
|
-
for (let i = 0; i < this.markedDims.length; i++) {
|
|
755
|
-
this.tmpVarCode.push(` }`)
|
|
756
|
-
}
|
|
757
|
-
this.callStack.pop()
|
|
758
|
-
// Reset state variables that were set down in the parse tree.
|
|
759
|
-
this.markedDims = []
|
|
760
|
-
this.arrayFunctionCode = ''
|
|
761
|
-
this.currentArrayFunctionName = ''
|
|
762
|
-
// Emit the temporary variable into the formula expression in place of the SUM call.
|
|
763
|
-
if (fn === '_VECTOR_SELECT') {
|
|
764
|
-
this.emit(`${condVar} ? ${tmpVar} : ${this.vsNullValue}`)
|
|
765
|
-
} else {
|
|
766
|
-
this.emit(tmpVar)
|
|
767
|
-
}
|
|
768
|
-
} else if (fn === '_VECTOR_ELM_MAP') {
|
|
769
|
-
super.visitCall(ctx)
|
|
770
|
-
this.callStack.pop()
|
|
771
|
-
this.emit(`${this.vemVarName}${this.vemSubscriptGen()}`)
|
|
772
|
-
this.vemVarName = ''
|
|
773
|
-
this.vemSubscripts = []
|
|
774
|
-
this.vemIndexDim = ''
|
|
775
|
-
this.vemIndexBase = 0
|
|
776
|
-
this.vemOffset = ''
|
|
777
|
-
} else if (fn === '_VECTOR_SORT_ORDER') {
|
|
778
|
-
super.visitCall(ctx)
|
|
779
|
-
let dimSize = sub(this.vsoTmpDimName).size
|
|
780
|
-
let vso = ` double* ${this.vsoTmpName} = _VECTOR_SORT_ORDER(${this.vsoVarName}, ${dimSize}, ${this.vsoOrder});`
|
|
781
|
-
// Inject the VSO call into the loop opening code that was aleady emitted into that channel.
|
|
782
|
-
this.subscriptLoopOpeningCode.splice(this.var.subscripts.length - 1, 0, vso)
|
|
783
|
-
this.callStack.pop()
|
|
784
|
-
this.vsoVarName = ''
|
|
785
|
-
this.vsoOrder = ''
|
|
786
|
-
this.vsoTmpName = ''
|
|
787
|
-
this.vsoTmpDimName = ''
|
|
788
|
-
} else if (fn === '_ALLOCATE_AVAILABLE') {
|
|
789
|
-
super.visitCall(ctx)
|
|
790
|
-
let dimSize = sub(this.aaTmpDimName).size
|
|
791
|
-
let aa = ` double* ${this.aaTmpName} = _ALLOCATE_AVAILABLE(${this.aaRequestArray}, (double*)${this.aaPriorityArray}, ${this.aaAvailableResource}, ${dimSize});`
|
|
792
|
-
// Inject the AA call into the loop opening code that was aleady emitted into that channel.
|
|
793
|
-
this.subscriptLoopOpeningCode.splice(this.var.subscripts.length - 1, 0, aa)
|
|
794
|
-
this.callStack.pop()
|
|
795
|
-
this.aaRequestArray = ''
|
|
796
|
-
this.aaPriorityArray = ''
|
|
797
|
-
this.aaAvailableResource = ''
|
|
798
|
-
this.aaTmpName = ''
|
|
799
|
-
this.aaTmpDimName = ''
|
|
800
|
-
} else if (fn === '_GET_DATA_BETWEEN_TIMES') {
|
|
801
|
-
this.emit('_GET_DATA_BETWEEN_TIMES(')
|
|
802
|
-
super.visitCall(ctx)
|
|
803
|
-
this.emit(')')
|
|
804
|
-
this.callStack.pop()
|
|
805
|
-
} else if (this.functionIsLookup() || this.var.isData()) {
|
|
806
|
-
// A lookup has function syntax but lookup semantics. Convert the function call into a lookup call.
|
|
807
|
-
this.emit(`_LOOKUP(${this.lookupName()}, `)
|
|
808
|
-
super.visitCall(ctx)
|
|
809
|
-
this.emit(')')
|
|
810
|
-
this.callStack.pop()
|
|
811
|
-
} else if (fn === '_ACTIVE_INITIAL') {
|
|
812
|
-
// Only emit the eval-time initialization without the function call for ACTIVE INITIAL.
|
|
813
|
-
super.visitCall(ctx)
|
|
814
|
-
} else if (fn === '_IF_THEN_ELSE') {
|
|
815
|
-
// Conditional expressions are handled specially in `visitExprList`.
|
|
816
|
-
super.visitCall(ctx)
|
|
817
|
-
this.callStack.pop()
|
|
818
|
-
} else if (isSmoothFunction(fn)) {
|
|
819
|
-
// For smooth functions, replace the entire call with the expansion variable generated earlier.
|
|
820
|
-
let smoothVar = Model.varWithRefId(this.var.smoothVarRefId)
|
|
821
|
-
this.emit(smoothVar.varName)
|
|
822
|
-
this.emit(this.rhsSubscriptGen(smoothVar.subscripts))
|
|
823
|
-
} else if (isTrendFunction(fn)) {
|
|
824
|
-
// For trend functions, replace the entire call with the expansion variable generated earlier.
|
|
825
|
-
let trendVar = Model.varWithRefId(this.var.trendVarName)
|
|
826
|
-
let rhsSubs = this.rhsSubscriptGen(trendVar.subscripts)
|
|
827
|
-
this.emit(`${this.var.trendVarName}${rhsSubs}`)
|
|
828
|
-
} else if (isNpvFunction(fn)) {
|
|
829
|
-
// For NPV functions, replace the entire call with the expansion variable generated earlier.
|
|
830
|
-
let npvVar = Model.varWithRefId(this.var.npvVarName)
|
|
831
|
-
let rhsSubs = this.rhsSubscriptGen(npvVar.subscripts)
|
|
832
|
-
this.emit(`${this.var.npvVarName}${rhsSubs}`)
|
|
833
|
-
} else if (isDelayFunction(fn)) {
|
|
834
|
-
// For delay functions, replace the entire call with the expansion variable generated earlier.
|
|
835
|
-
let delayVar = Model.varWithRefId(this.var.delayVarRefId)
|
|
836
|
-
let rhsSubs = this.rhsSubscriptGen(delayVar.subscripts)
|
|
837
|
-
this.emit(`(${delayVar.varName}${rhsSubs} / ${this.var.delayTimeVarName}${rhsSubs})`)
|
|
838
|
-
} else {
|
|
839
|
-
// Generate code for ordinary function calls here.
|
|
840
|
-
this.emit(fn)
|
|
841
|
-
this.emit('(')
|
|
842
|
-
super.visitCall(ctx)
|
|
843
|
-
this.emit(')')
|
|
844
|
-
this.callStack.pop()
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
visitExprList(ctx) {
|
|
848
|
-
let exprs = ctx.expr()
|
|
849
|
-
let fn = this.currentFunctionName()
|
|
850
|
-
// Split level functions into init and eval expressions.
|
|
851
|
-
if (
|
|
852
|
-
fn === '_INTEG' ||
|
|
853
|
-
fn === '_SAMPLE_IF_TRUE' ||
|
|
854
|
-
fn === '_ACTIVE_INITIAL' ||
|
|
855
|
-
fn === '_DELAY_FIXED' ||
|
|
856
|
-
fn === '_DEPRECIATE_STRAIGHTLINE'
|
|
857
|
-
) {
|
|
858
|
-
if (this.mode.startsWith('init')) {
|
|
859
|
-
// Get the index of the argument holding the initial value.
|
|
860
|
-
let i = 0
|
|
861
|
-
if (fn === '_INTEG' || fn === '_ACTIVE_INITIAL') {
|
|
862
|
-
i = 1
|
|
863
|
-
} else if (fn === '_SAMPLE_IF_TRUE' || fn === '_DELAY_FIXED') {
|
|
864
|
-
i = 2
|
|
865
|
-
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
866
|
-
i = 3
|
|
867
|
-
}
|
|
868
|
-
this.setArgIndex(i)
|
|
869
|
-
exprs[i].accept(this)
|
|
870
|
-
// For DELAY FIXED and DEPRECIATE STRAIGHTLINE, also initialize the support struct
|
|
871
|
-
// out of band, as they are not Vensim vars.
|
|
872
|
-
if (fn === '_DELAY_FIXED') {
|
|
873
|
-
let fixedDelay = `${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)}`
|
|
874
|
-
this.emit(`;\n ${fixedDelay} = __new_fixed_delay(${fixedDelay}, `)
|
|
875
|
-
this.setArgIndex(1)
|
|
876
|
-
exprs[1].accept(this)
|
|
877
|
-
this.emit(', ')
|
|
878
|
-
this.setArgIndex(2)
|
|
879
|
-
exprs[2].accept(this)
|
|
880
|
-
this.emit(')')
|
|
881
|
-
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
882
|
-
let depreciation = `${this.var.depreciationVarName}${this.lhsSubscriptGen(this.var.subscripts)}`
|
|
883
|
-
this.emit(`;\n ${depreciation} = __new_depreciation(${depreciation}, `)
|
|
884
|
-
this.setArgIndex(1)
|
|
885
|
-
exprs[1].accept(this)
|
|
886
|
-
this.emit(', ')
|
|
887
|
-
this.setArgIndex(2)
|
|
888
|
-
exprs[3].accept(this)
|
|
889
|
-
this.emit(')')
|
|
890
|
-
}
|
|
891
|
-
} else {
|
|
892
|
-
// We are in eval mode, not init mode.
|
|
893
|
-
if (fn === '_ACTIVE_INITIAL') {
|
|
894
|
-
// For ACTIVE INITIAL, emit the first arg without a function call.
|
|
895
|
-
this.setArgIndex(0)
|
|
896
|
-
exprs[0].accept(this)
|
|
897
|
-
} else if (fn === '_DELAY_FIXED') {
|
|
898
|
-
// For DELAY FIXED, emit the first arg followed by the FixedDelay support var.
|
|
899
|
-
this.setArgIndex(0)
|
|
900
|
-
exprs[0].accept(this)
|
|
901
|
-
this.emit(', ')
|
|
902
|
-
this.emit(`${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)}`)
|
|
903
|
-
} else if (fn === '_DEPRECIATE_STRAIGHTLINE') {
|
|
904
|
-
// For DEPRECIATE STRAIGHTLINE, emit the first arg followed by the Depreciation support var.
|
|
905
|
-
this.setArgIndex(0)
|
|
906
|
-
exprs[0].accept(this)
|
|
907
|
-
this.emit(', ')
|
|
908
|
-
this.emit(`${this.var.depreciationVarName}${this.lhsSubscriptGen(this.var.subscripts)}`)
|
|
909
|
-
} else {
|
|
910
|
-
// Emit the variable LHS as the first arg at eval time, giving the current value for the level.
|
|
911
|
-
this.emit(this.lhs)
|
|
912
|
-
this.emit(', ')
|
|
913
|
-
// Emit the remaining arguments by visiting each expression in the list.
|
|
914
|
-
this.setArgIndex(0)
|
|
915
|
-
exprs[0].accept(this)
|
|
916
|
-
if (fn === '_SAMPLE_IF_TRUE') {
|
|
917
|
-
this.emit(', ')
|
|
918
|
-
this.setArgIndex(1)
|
|
919
|
-
exprs[1].accept(this)
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
} else if (fn === '_VECTOR_SELECT') {
|
|
924
|
-
this.setArgIndex(0)
|
|
925
|
-
exprs[0].accept(this)
|
|
926
|
-
this.setArgIndex(1)
|
|
927
|
-
exprs[1].accept(this)
|
|
928
|
-
this.setArgIndex(2)
|
|
929
|
-
this.vsNullValue = this.cVarOrConst(exprs[2])
|
|
930
|
-
// TODO implement other actions besides just sum and max
|
|
931
|
-
this.setArgIndex(3)
|
|
932
|
-
this.vsAction = this.constValue(exprs[3].getText().trim())
|
|
933
|
-
// TODO obey the error handling instruction here
|
|
934
|
-
this.setArgIndex(4)
|
|
935
|
-
this.vsError = this.cVarOrConst(exprs[4])
|
|
936
|
-
} else if (fn === '_VECTOR_ELM_MAP') {
|
|
937
|
-
this.setArgIndex(0)
|
|
938
|
-
exprs[0].accept(this)
|
|
939
|
-
this.setArgIndex(1)
|
|
940
|
-
exprs[1].accept(this)
|
|
941
|
-
} else if (fn === '_VECTOR_SORT_ORDER') {
|
|
942
|
-
this.setArgIndex(0)
|
|
943
|
-
exprs[0].accept(this)
|
|
944
|
-
this.setArgIndex(1)
|
|
945
|
-
this.vsoOrder = this.cVarOrConst(exprs[1])
|
|
946
|
-
} else if (fn === '_ALLOCATE_AVAILABLE') {
|
|
947
|
-
this.setArgIndex(0)
|
|
948
|
-
exprs[0].accept(this)
|
|
949
|
-
this.setArgIndex(1)
|
|
950
|
-
exprs[1].accept(this)
|
|
951
|
-
this.setArgIndex(2)
|
|
952
|
-
this.aaAvailableResource = this.cVarOrConst(exprs[2])
|
|
953
|
-
} else if (fn === '_IF_THEN_ELSE') {
|
|
954
|
-
// See if the condition expression was previously determined to resolve to a
|
|
955
|
-
// compile-time constant. If so, we only need to emit code for one branch.
|
|
956
|
-
const condText = ctx.expr(0).getText()
|
|
957
|
-
const condValue = Model.getConstantExprValue(condText)
|
|
958
|
-
if (condValue !== undefined) {
|
|
959
|
-
this.emit('(')
|
|
960
|
-
if (condValue !== 0) {
|
|
961
|
-
// Emit only the "if true" branch
|
|
962
|
-
this.setArgIndex(1)
|
|
963
|
-
ctx.expr(1).accept(this)
|
|
964
|
-
} else {
|
|
965
|
-
// Emit only the "if false" branch
|
|
966
|
-
this.setArgIndex(2)
|
|
967
|
-
ctx.expr(2).accept(this)
|
|
968
|
-
}
|
|
969
|
-
this.emit(')')
|
|
970
|
-
} else {
|
|
971
|
-
// Emit a normal if/else with both branches
|
|
972
|
-
this.emit(fn)
|
|
973
|
-
this.emit('(')
|
|
974
|
-
for (let i = 0; i < exprs.length; i++) {
|
|
975
|
-
if (i > 0) this.emit(', ')
|
|
976
|
-
this.setArgIndex(i)
|
|
977
|
-
exprs[i].accept(this)
|
|
978
|
-
}
|
|
979
|
-
this.emit(')')
|
|
980
|
-
}
|
|
981
|
-
} else {
|
|
982
|
-
// Ordinary expression lists are completely emitted with comma delimiters.
|
|
983
|
-
for (let i = 0; i < exprs.length; i++) {
|
|
984
|
-
if (i > 0) this.emit(', ')
|
|
985
|
-
this.setArgIndex(i)
|
|
986
|
-
exprs[i].accept(this)
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
visitVar(ctx) {
|
|
991
|
-
// Helper function that emits a lookup call if the variable is a data variable,
|
|
992
|
-
// otherwise emits a normal variable.
|
|
993
|
-
const emitVar = () => {
|
|
994
|
-
let v = Model.varWithName(this.currentVarName())
|
|
995
|
-
if (v && v.varType === 'data') {
|
|
996
|
-
this.emit(`_LOOKUP(${this.currentVarName()}`)
|
|
997
|
-
super.visitVar(ctx)
|
|
998
|
-
this.emit(', _time)')
|
|
999
|
-
} else {
|
|
1000
|
-
this.emit(this.currentVarName())
|
|
1001
|
-
super.visitVar(ctx)
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// Push the var name on the stack and then emit it.
|
|
1006
|
-
let id = ctx.Id().getText()
|
|
1007
|
-
let varName = canonicalName(id)
|
|
1008
|
-
let functionName = this.currentFunctionName()
|
|
1009
|
-
if (isDimension(varName)) {
|
|
1010
|
-
if (functionName === '_ELMCOUNT') {
|
|
1011
|
-
// Emit the size of the dimension in place of the dimension name.
|
|
1012
|
-
this.emit(`${sub(varName).size}`)
|
|
1013
|
-
} else {
|
|
1014
|
-
// A dimension masquerading as a variable (i.e., in expression position) takes the
|
|
1015
|
-
// value of the loop index var plus one (since Vensim indices are one-based).
|
|
1016
|
-
let s = this.rhsSubscriptGen([varName])
|
|
1017
|
-
// Remove the brackets around the C subscript expression.
|
|
1018
|
-
s = s.slice(1, s.length - 1)
|
|
1019
|
-
this.emit(`(${s} + 1)`)
|
|
1020
|
-
}
|
|
1021
|
-
} else if (isIndex(varName)) {
|
|
1022
|
-
// A subscript masquerading as a variable (i.e., in expression position) takes the
|
|
1023
|
-
// numeric index value plus one (since Vensim indices are one-based).
|
|
1024
|
-
const index = sub(varName).value
|
|
1025
|
-
this.emit(`${index + 1}`)
|
|
1026
|
-
} else {
|
|
1027
|
-
this.varNames.push(varName)
|
|
1028
|
-
if (functionName === '_VECTOR_SELECT') {
|
|
1029
|
-
let argIndex = this.argIndexForFunctionName(functionName)
|
|
1030
|
-
if (argIndex === 0) {
|
|
1031
|
-
this.vsSelectionArray = this.currentVarName()
|
|
1032
|
-
super.visitVar(ctx)
|
|
1033
|
-
} else if (argIndex === 1) {
|
|
1034
|
-
emitVar()
|
|
1035
|
-
} else {
|
|
1036
|
-
super.visitVar(ctx)
|
|
1037
|
-
}
|
|
1038
|
-
} else if (functionName === '_VECTOR_ELM_MAP') {
|
|
1039
|
-
if (this.argIndexForFunctionName(functionName) === 1) {
|
|
1040
|
-
this.vemOffset = this.currentVarName()
|
|
1041
|
-
}
|
|
1042
|
-
super.visitVar(ctx)
|
|
1043
|
-
} else if (functionName === '_VECTOR_SORT_ORDER') {
|
|
1044
|
-
if (this.argIndexForFunctionName(functionName) === 0) {
|
|
1045
|
-
this.vsoVarName = this.currentVarName()
|
|
1046
|
-
this.vsoTmpName = newTmpVarName()
|
|
1047
|
-
this.emit(this.vsoTmpName)
|
|
1048
|
-
}
|
|
1049
|
-
super.visitVar(ctx)
|
|
1050
|
-
} else if (functionName === '_ALLOCATE_AVAILABLE') {
|
|
1051
|
-
if (this.argIndexForFunctionName(functionName) === 0) {
|
|
1052
|
-
this.aaRequestArray = this.currentVarName()
|
|
1053
|
-
this.aaTmpName = newTmpVarName()
|
|
1054
|
-
this.emit(this.aaTmpName)
|
|
1055
|
-
} else if (this.argIndexForFunctionName(functionName) === 1) {
|
|
1056
|
-
this.aaPriorityArray = this.currentVarName()
|
|
1057
|
-
}
|
|
1058
|
-
super.visitVar(ctx)
|
|
1059
|
-
} else if (functionName === '_GET_DATA_BETWEEN_TIMES') {
|
|
1060
|
-
this.emit(this.currentVarName())
|
|
1061
|
-
super.visitVar(ctx)
|
|
1062
|
-
} else if (
|
|
1063
|
-
functionName === '_LOOKUP_FORWARD' ||
|
|
1064
|
-
functionName === '_LOOKUP_BACKWARD' ||
|
|
1065
|
-
functionName === '_LOOKUP_INVERT'
|
|
1066
|
-
) {
|
|
1067
|
-
let argIndex = this.argIndexForFunctionName(functionName)
|
|
1068
|
-
if (argIndex === 0) {
|
|
1069
|
-
this.emit(this.currentVarName())
|
|
1070
|
-
super.visitVar(ctx)
|
|
1071
|
-
} else {
|
|
1072
|
-
emitVar()
|
|
1073
|
-
}
|
|
1074
|
-
} else {
|
|
1075
|
-
emitVar()
|
|
1076
|
-
}
|
|
1077
|
-
this.varNames.pop()
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
visitLookupArg() {
|
|
1081
|
-
// Substitute the previously generated lookup arg var name into the expression.
|
|
1082
|
-
if (this.var.lookupArgVarName) {
|
|
1083
|
-
this.emit(this.var.lookupArgVarName)
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
visitLookupCall(ctx) {
|
|
1087
|
-
// Make a lookup argument into a _LOOKUP function call.
|
|
1088
|
-
let id = ctx.Id().getText()
|
|
1089
|
-
this.varNames.push(canonicalName(id))
|
|
1090
|
-
this.emit(`_LOOKUP(${canonicalName(id)}`)
|
|
1091
|
-
// Emit subscripts after the var name, if any.
|
|
1092
|
-
super.visitLookupCall(ctx)
|
|
1093
|
-
this.emit(', ')
|
|
1094
|
-
ctx.expr().accept(this)
|
|
1095
|
-
this.emit(')')
|
|
1096
|
-
this.varNames.pop()
|
|
1097
|
-
}
|
|
1098
|
-
visitSubscriptList(ctx) {
|
|
1099
|
-
// Emit subscripts for a variable occurring on the RHS.
|
|
1100
|
-
if (ctx.parentCtx.ruleIndex === ModelParser.RULE_expr) {
|
|
1101
|
-
let subscripts = R.map(id => canonicalName(id.getText()), ctx.Id())
|
|
1102
|
-
let mergeMarkedDims = () => {
|
|
1103
|
-
// Extract all marked dimensions and update subscripts.
|
|
1104
|
-
let dims = extractMarkedDims(subscripts)
|
|
1105
|
-
// Merge marked dims that were found into the list for this call.
|
|
1106
|
-
this.markedDims = R.uniq(R.concat(this.markedDims, dims))
|
|
1107
|
-
}
|
|
1108
|
-
let fn = this.currentFunctionName()
|
|
1109
|
-
let arrayFn = this.currentArrayFunctionName
|
|
1110
|
-
if (arrayFn === '_SUM' || arrayFn === '_VMIN' || arrayFn === '_VMAX') {
|
|
1111
|
-
mergeMarkedDims()
|
|
1112
|
-
this.emit(this.rhsSubscriptGen(subscripts))
|
|
1113
|
-
} else if (arrayFn === '_VECTOR_SELECT') {
|
|
1114
|
-
let argIndex = this.argIndexForFunctionName('_VECTOR_SELECT')
|
|
1115
|
-
if (argIndex === 0) {
|
|
1116
|
-
mergeMarkedDims()
|
|
1117
|
-
this.vsSelectionArray += this.rhsSubscriptGen(subscripts)
|
|
1118
|
-
} else if (argIndex === 1) {
|
|
1119
|
-
mergeMarkedDims()
|
|
1120
|
-
this.emit(this.rhsSubscriptGen(subscripts))
|
|
1121
|
-
}
|
|
1122
|
-
} else if (fn === '_VECTOR_ELM_MAP') {
|
|
1123
|
-
if (this.argIndexForFunctionName('_VECTOR_ELM_MAP') === 0) {
|
|
1124
|
-
this.vemVarName = this.currentVarName()
|
|
1125
|
-
// Gather information from the argument to generate code later.
|
|
1126
|
-
// The marked dim is an index in the vector argument.
|
|
1127
|
-
this.vemSubscripts = subscripts
|
|
1128
|
-
for (let subscript of subscripts) {
|
|
1129
|
-
if (isIndex(subscript)) {
|
|
1130
|
-
let ind = sub(subscript)
|
|
1131
|
-
this.vemIndexDim = ind.family
|
|
1132
|
-
this.vemIndexBase = ind.value
|
|
1133
|
-
break
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
} else {
|
|
1137
|
-
// Add subscripts to the offset argument.
|
|
1138
|
-
this.vemOffset += this.rhsSubscriptGen(subscripts)
|
|
1139
|
-
}
|
|
1140
|
-
} else if (fn === '_VECTOR_SORT_ORDER') {
|
|
1141
|
-
if (this.argIndexForFunctionName('_VECTOR_SORT_ORDER') === 0) {
|
|
1142
|
-
this.vsoSubscriptGen(subscripts)
|
|
1143
|
-
}
|
|
1144
|
-
} else if (fn === '_ALLOCATE_AVAILABLE') {
|
|
1145
|
-
if (this.argIndexForFunctionName('_ALLOCATE_AVAILABLE') === 0) {
|
|
1146
|
-
this.aaSubscriptGen(subscripts)
|
|
1147
|
-
}
|
|
1148
|
-
} else {
|
|
1149
|
-
// Add C subscripts to the variable name that was already emitted.
|
|
1150
|
-
this.emit(this.rhsSubscriptGen(subscripts))
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
visitConstList(ctx) {
|
|
1155
|
-
let emitConstAtPos = i => {
|
|
1156
|
-
this.emit(strToConst(exprs[i].getText()))
|
|
1157
|
-
}
|
|
1158
|
-
let exprs = ctx.expr()
|
|
1159
|
-
// console.error(`visitConstList ${this.var.refId} ${exprs.length} exprs`)
|
|
1160
|
-
if (exprs.length === 1) {
|
|
1161
|
-
// Emit a single constant into the expression code.
|
|
1162
|
-
emitConstAtPos(0)
|
|
1163
|
-
} else {
|
|
1164
|
-
// All const lists with > 1 value are separated on dimensions in the LHS.
|
|
1165
|
-
// The LHS of a separated variable here will contain only index subscripts in normal order.
|
|
1166
|
-
// Calculate an index into a flattened array by converting the indices to numeric form and looking them up
|
|
1167
|
-
// in a C name array listed in the same Vensim order as the constant array in the model.
|
|
1168
|
-
let modelLHSReader = new ModelLHSReader()
|
|
1169
|
-
modelLHSReader.read(this.var.modelLHS)
|
|
1170
|
-
let cNames = modelLHSReader.names().map(Model.cName)
|
|
1171
|
-
let cVarName = this.var.varName + R.map(indName => `[${sub(indName).value}]`, this.var.subscripts).join('')
|
|
1172
|
-
// Find the position of the constant in Vensim order from the expanded LHS var list.
|
|
1173
|
-
let constPos = R.indexOf(cVarName, cNames)
|
|
1174
|
-
if (constPos >= 0) {
|
|
1175
|
-
emitConstAtPos(constPos)
|
|
1176
|
-
// console.error(`${this.var.refId} position = ${constPos}`)
|
|
1177
|
-
} else {
|
|
1178
|
-
console.error(`ERROR: const list element ${this.var.refId} → ${cVarName} not found in C names`)
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
//
|
|
1183
|
-
// Operators, etc.
|
|
1184
|
-
//
|
|
1185
|
-
visitNegative(ctx) {
|
|
1186
|
-
this.emit('-')
|
|
1187
|
-
super.visitNegative(ctx)
|
|
1188
|
-
}
|
|
1189
|
-
visitNot(ctx) {
|
|
1190
|
-
this.emit('!')
|
|
1191
|
-
super.visitNot(ctx)
|
|
1192
|
-
}
|
|
1193
|
-
visitPower(ctx) {
|
|
1194
|
-
this.emit('pow(')
|
|
1195
|
-
ctx.expr(0).accept(this)
|
|
1196
|
-
this.emit(', ')
|
|
1197
|
-
ctx.expr(1).accept(this)
|
|
1198
|
-
this.emit(')')
|
|
1199
|
-
}
|
|
1200
|
-
visitMulDiv(ctx) {
|
|
1201
|
-
ctx.expr(0).accept(this)
|
|
1202
|
-
if (ctx.op.type === ModelLexer.Star) {
|
|
1203
|
-
this.emit(' * ')
|
|
1204
|
-
} else {
|
|
1205
|
-
this.emit(' / ')
|
|
1206
|
-
}
|
|
1207
|
-
ctx.expr(1).accept(this)
|
|
1208
|
-
}
|
|
1209
|
-
visitAddSub(ctx) {
|
|
1210
|
-
ctx.expr(0).accept(this)
|
|
1211
|
-
if (ctx.op.type === ModelLexer.Plus) {
|
|
1212
|
-
this.emit(' + ')
|
|
1213
|
-
} else {
|
|
1214
|
-
this.emit(' - ')
|
|
1215
|
-
}
|
|
1216
|
-
ctx.expr(1).accept(this)
|
|
1217
|
-
}
|
|
1218
|
-
visitRelational(ctx) {
|
|
1219
|
-
ctx.expr(0).accept(this)
|
|
1220
|
-
if (ctx.op.type === ModelLexer.Less) {
|
|
1221
|
-
this.emit(' < ')
|
|
1222
|
-
} else if (ctx.op.type === ModelLexer.Greater) {
|
|
1223
|
-
this.emit(' > ')
|
|
1224
|
-
} else if (ctx.op.type === ModelLexer.LessEqual) {
|
|
1225
|
-
this.emit(' <= ')
|
|
1226
|
-
} else {
|
|
1227
|
-
this.emit(' >= ')
|
|
1228
|
-
}
|
|
1229
|
-
ctx.expr(1).accept(this)
|
|
1230
|
-
}
|
|
1231
|
-
visitEquality(ctx) {
|
|
1232
|
-
ctx.expr(0).accept(this)
|
|
1233
|
-
if (ctx.op.type === ModelLexer.Equal) {
|
|
1234
|
-
this.emit(' == ')
|
|
1235
|
-
} else {
|
|
1236
|
-
this.emit(' != ')
|
|
1237
|
-
}
|
|
1238
|
-
ctx.expr(1).accept(this)
|
|
1239
|
-
}
|
|
1240
|
-
visitAnd(ctx) {
|
|
1241
|
-
ctx.expr(0).accept(this)
|
|
1242
|
-
this.emit(' && ')
|
|
1243
|
-
ctx.expr(1).accept(this)
|
|
1244
|
-
}
|
|
1245
|
-
visitOr(ctx) {
|
|
1246
|
-
ctx.expr(0).accept(this)
|
|
1247
|
-
this.emit(' || ')
|
|
1248
|
-
ctx.expr(1).accept(this)
|
|
1249
|
-
}
|
|
1250
|
-
visitKeyword(ctx) {
|
|
1251
|
-
var keyword = ctx.Keyword().getText()
|
|
1252
|
-
if (keyword === ':NA:') {
|
|
1253
|
-
keyword = '_NA_'
|
|
1254
|
-
} else if (keyword === ':INTERPOLATE:') {
|
|
1255
|
-
keyword = ''
|
|
1256
|
-
}
|
|
1257
|
-
this.emit(keyword)
|
|
1258
|
-
}
|
|
1259
|
-
visitConst(ctx) {
|
|
1260
|
-
let c = ctx.Const().getText()
|
|
1261
|
-
this.emit(strToConst(c))
|
|
1262
|
-
}
|
|
1263
|
-
visitParens(ctx) {
|
|
1264
|
-
this.emit('(')
|
|
1265
|
-
super.visitParens(ctx)
|
|
1266
|
-
this.emit(')')
|
|
1267
|
-
}
|
|
1268
|
-
}
|