@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.
@@ -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
- }