@sdeverywhere/compile 0.7.10 → 0.7.12

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.
@@ -0,0 +1,851 @@
1
+ import { cdbl, newTmpVarName } from '../_shared/helpers.js'
2
+ import { extractMarkedDims, isDimension, isIndex, normalizeSubscripts, sub } from '../_shared/subscript.js'
3
+
4
+ import Model from '../model/model.js'
5
+
6
+ /**
7
+ * @typedef {Object} GenExprContext The context for a `generateExpr` call.
8
+ *
9
+ * @param {*} variable The `Variable` instance to process.
10
+ * @param {'decl' | 'init-constants' | 'init-lookups' | 'init-levels' | 'eval'} mode The code generation mode.
11
+ * @param {string} cLhs The C code for the LHS variable reference.
12
+ * @param {LoopIndexVars} loopIndexVars The loop index state used for LHS dimensions.
13
+ * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions (that use marked dimensions).
14
+ * @param {() => void} resetMarkedDims Function that resets the marked dimension state.
15
+ * @param {(dimId: string) => void} addMarkedDim Function that adds the given dimension to the set of marked dimensions.
16
+ * @param {(s: string) => void} emitPreInnerLoop Function that will cause the given code to be appended to the chunk that
17
+ * precedes the generated inner loop for the equation.
18
+ * @param {(s: string) => void} emitPreFormula Function that will cause the given code to be appended to the chunk that
19
+ * precedes the generated formula (the primary, inner-most part of the equation).
20
+ * @param {(s: string) => void} emitPostFormula Function that will cause the given code to be appended to the chunk that
21
+ * follows the generated formula (the primary, inner-most part of the equation).
22
+ * @param {(varRef: VariableRef) => string} cVarRef Function that returns a C variable reference for a variable
23
+ * referenced in a RHS expression.
24
+ * @param {(baseVarId: string) => string} cVarRefWithLhsSubscripts Function that returns a C variable reference that
25
+ * takes into account the relevant LHS subscripts.
26
+ * @param {(subOrDimId: string) => string} cVarIndex Function that returns C code for indexing into a subscripted variable.
27
+ */
28
+
29
+ /**
30
+ * Generate the RHS code for the given expression.
31
+ *
32
+ * TODO: Types
33
+ *
34
+ * @param {*} expr The expression from the parsed model.
35
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
36
+ * @return {string} The generated C code.
37
+ */
38
+ export function generateExpr(expr, ctx) {
39
+ switch (expr.kind) {
40
+ case 'number':
41
+ return cdbl(expr.value)
42
+
43
+ case 'string':
44
+ return `'${expr.text}'`
45
+
46
+ case 'keyword':
47
+ if (expr.text === ':NA:') {
48
+ return '_NA_'
49
+ } else {
50
+ throw new Error(`Unhandled keyword '${expr.text}' in code gen for '${ctx.variable.modelLHS}'`)
51
+ }
52
+
53
+ case 'variable-ref': {
54
+ // This is a variable or dimension reference. See if there is a variable defined for the ID.
55
+ const v = Model.varWithName(expr.varId)
56
+ if (v) {
57
+ // This is a reference to a known variable
58
+ if (v.isData()) {
59
+ // It's a data variable; transform to a `_LOOKUP` function call
60
+ return `_LOOKUP(${ctx.cVarRef(expr)}, _time)`
61
+ } else {
62
+ // It's not a data variable; generate a normal variable reference
63
+ return ctx.cVarRef(expr)
64
+ }
65
+ } else if (isDimension(expr.varId)) {
66
+ // This is a reference to a dimension that is being used in expression position.
67
+ // In place of the dimension, emit the current value of the loop index variable
68
+ // plus one (since Vensim indices are one-based).
69
+ const dimId = expr.varId
70
+ const indexCode = ctx.cVarIndex(dimId)
71
+ return `(${indexCode} + 1)`
72
+ } else if (isIndex(expr.varId)) {
73
+ // This is a reference to a subscript/index that is being used in expression position.
74
+ // In place of the subscript, emit the numeric index value of the subscript plus one
75
+ // (since Vensim indices are one-based).
76
+ const subId = expr.varId
77
+ const indexValue = sub(subId).value
78
+ return `${indexValue + 1}`
79
+ } else {
80
+ throw new Error(`Unresolved variable reference '${expr.varName}' in code gen for '${ctx.variable.modelLHS}'`)
81
+ }
82
+ }
83
+
84
+ case 'unary-op': {
85
+ let op
86
+ switch (expr.op) {
87
+ case ':NOT:':
88
+ op = '!'
89
+ break
90
+ case '+':
91
+ // We can drop the explicit '+' prefix in this case
92
+ op = ''
93
+ break
94
+ default:
95
+ op = expr.op
96
+ break
97
+ }
98
+ return `${op}${generateExpr(expr.expr, ctx)}`
99
+ }
100
+
101
+ case 'binary-op': {
102
+ const lhs = generateExpr(expr.lhs, ctx)
103
+ const rhs = generateExpr(expr.rhs, ctx)
104
+ if (expr.op === '^') {
105
+ return `pow(${lhs}, ${rhs})`
106
+ } else {
107
+ let op
108
+ switch (expr.op) {
109
+ case '=':
110
+ op = '=='
111
+ break
112
+ case '<>':
113
+ op = '!='
114
+ break
115
+ case ':AND:':
116
+ op = '&&'
117
+ break
118
+ case ':OR:':
119
+ op = '||'
120
+ break
121
+ default:
122
+ op = expr.op
123
+ break
124
+ }
125
+ return `${lhs} ${op} ${rhs}`
126
+ }
127
+ }
128
+
129
+ case 'parens':
130
+ return `(${generateExpr(expr.expr, ctx)})`
131
+
132
+ case 'lookup-def':
133
+ // Lookup defs in expression position should only occur in the case of `WITH LOOKUP`
134
+ // function calls, and those are transformed into a generated lookup variable during
135
+ // `readEquations`, so replace the def with a reference to the generated variable
136
+ return ctx.variable.lookupArgVarName
137
+
138
+ case 'lookup-call':
139
+ // For Vensim models, the antlr4-vensim grammar has separate definitions for lookup
140
+ // calls and function calls, but in practice they can only be differentiated in the
141
+ // case where the lookup has subscripts; when there are no subscripts, they get
142
+ // treated like normal function calls. Therefore we need to handle these in two
143
+ // places. The code here deals with lookup calls that involve a lookup with one
144
+ // or more dimensions. The `default` case in `generateFunctionCall` deals with
145
+ // lookup calls that involve a non-subscripted lookup variable.
146
+ return generateLookupCall(expr.varRef, expr.arg, ctx)
147
+
148
+ case 'function-call':
149
+ return generateFunctionCall(expr, ctx)
150
+
151
+ default:
152
+ throw new Error(`Unhandled expression kind '${expr.kind}' when reading '${ctx.variable.modelLHS}'`)
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Generate C code for the given function call.
158
+ *
159
+ * TODO: Types
160
+ *
161
+ * @param {*} callExpr The function call expression from the parsed model.
162
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
163
+ * @return {string} The generated C code.
164
+ */
165
+ function generateFunctionCall(callExpr, ctx) {
166
+ const fnId = callExpr.fnId
167
+
168
+ switch (fnId) {
169
+ //
170
+ //
171
+ // Simple functions
172
+ //
173
+ // Each of these functions is implemented with a C function or macro, so no further processing
174
+ // is required other than to emit the C function/macro call.
175
+ //
176
+ //
177
+
178
+ case '_ABS':
179
+ case '_ARCCOS':
180
+ case '_ARCSIN':
181
+ case '_ARCTAN':
182
+ case '_COS':
183
+ case '_EXP':
184
+ case '_GAME':
185
+ case '_GAMMA_LN':
186
+ case '_IF_THEN_ELSE':
187
+ case '_INTEGER':
188
+ case '_LN':
189
+ case '_MAX':
190
+ case '_MIN':
191
+ case '_MODULO':
192
+ case '_POW':
193
+ case '_POWER':
194
+ case '_PULSE':
195
+ case '_PULSE_TRAIN':
196
+ case '_QUANTUM':
197
+ case '_RAMP':
198
+ case '_SIN':
199
+ case '_SQRT':
200
+ case '_STEP':
201
+ case '_TAN':
202
+ case '_WITH_LOOKUP':
203
+ case '_XIDZ':
204
+ case '_ZIDZ': {
205
+ // For simple functions, emit a C function call with a generated C expression for each argument
206
+ const args = callExpr.args.map(argExpr => generateExpr(argExpr, ctx))
207
+ return `${fnId}(${args.join(', ')})`
208
+ }
209
+
210
+ //
211
+ //
212
+ // Lookup functions
213
+ //
214
+ // Each of these functions is implemented with a C function (like the simple functions above),
215
+ // but we need to handle the first argument specially, otherwise we would get the default handling
216
+ // for data variables, which generates a lookup call (see 'variable-ref' case in `generateExpr`).
217
+ //
218
+ //
219
+
220
+ case '_GET_DATA_BETWEEN_TIMES':
221
+ case '_LOOKUP_BACKWARD':
222
+ case '_LOOKUP_FORWARD':
223
+ case '_LOOKUP_INVERT': {
224
+ // For LOOKUP* functions, the first argument must be a reference to the lookup variable. Emit
225
+ // a C function call with a generated C expression for each remaining argument.
226
+ const cVarRef = ctx.cVarRef(callExpr.args[0])
227
+ const cArgs = callExpr.args.slice(1).map(arg => generateExpr(arg, ctx))
228
+ return `${fnId}(${cVarRef}, ${cArgs.join(', ')})`
229
+ }
230
+
231
+ //
232
+ //
233
+ // Level functions
234
+ //
235
+ //
236
+
237
+ case '_ACTIVE_INITIAL':
238
+ case '_DELAY_FIXED':
239
+ case '_DEPRECIATE_STRAIGHTLINE':
240
+ case '_SAMPLE_IF_TRUE':
241
+ case '_INTEG':
242
+ // Split level functions into init and eval expressions
243
+ if (ctx.mode.startsWith('init')) {
244
+ return generateLevelInit(callExpr, ctx)
245
+ } else if (ctx.mode === 'eval') {
246
+ return generateLevelEval(callExpr, ctx)
247
+ } else {
248
+ throw new Error(`Invalid code gen mode '${ctx.mode}' for level variable '${ctx.variable.modelLHS}'`)
249
+ }
250
+
251
+ //
252
+ //
253
+ // Array functions
254
+ //
255
+ //
256
+
257
+ case '_VECTOR_SELECT':
258
+ case '_VMAX':
259
+ case '_VMIN':
260
+ case '_SUM':
261
+ return generateArrayFunctionCall(callExpr, ctx)
262
+
263
+ //
264
+ //
265
+ // Expanded functions
266
+ //
267
+ // Each of these function calls was expanded into multiple implementation variables
268
+ // during the `generateEquations` phase, so in place of the entire function call, we
269
+ // emit a reference to the expanded variable.
270
+ //
271
+ //
272
+
273
+ case '_DELAY1':
274
+ case '_DELAY1I':
275
+ case '_DELAY3':
276
+ case '_DELAY3I': {
277
+ const delayVar = Model.varWithRefId(ctx.variable.delayVarRefId)
278
+ const delayVarRef = ctx.cVarRef(delayVar.parsedEqn.lhs.varDef)
279
+ // TODO: For now, extract the RHS subscripts from the ones that were computed for the
280
+ // delay variable. We should add a variant of cVarRef that returns only the RHS subs.
281
+ // return `(${delayVar.varName}${rhsSubs} / ${ctx.variable.delayTimeVarName}${rhsSubs})`
282
+ const delayVarParts = delayVarRef.split('[')
283
+ const rhsSubs = delayVarParts.length > 1 ? `[${delayVarParts[1]}` : ''
284
+ return `(${delayVarRef} / ${ctx.variable.delayTimeVarName}${rhsSubs})`
285
+ }
286
+
287
+ case '_NPV': {
288
+ const npvVar = Model.varWithRefId(ctx.variable.npvVarName)
289
+ return ctx.cVarRef(npvVar.parsedEqn.lhs.varDef)
290
+ }
291
+
292
+ case '_SMOOTH':
293
+ case '_SMOOTHI':
294
+ case '_SMOOTH3':
295
+ case '_SMOOTH3I': {
296
+ const smoothVar = Model.varWithRefId(ctx.variable.smoothVarRefId)
297
+ return ctx.cVarRef(smoothVar.parsedEqn.lhs.varDef)
298
+ }
299
+
300
+ case '_TREND': {
301
+ const trendVar = Model.varWithRefId(ctx.variable.trendVarName)
302
+ return ctx.cVarRef(trendVar.parsedEqn.lhs.varDef)
303
+ }
304
+
305
+ //
306
+ //
307
+ // Vector functions
308
+ //
309
+ //
310
+
311
+ case '_VECTOR_ELM_MAP':
312
+ return generateVectorElmMapCall(callExpr, ctx)
313
+
314
+ case '_VECTOR_SORT_ORDER':
315
+ return generateVectorSortOrderCall(callExpr, ctx)
316
+
317
+ //
318
+ //
319
+ // Uncategorized functions
320
+ //
321
+ //
322
+
323
+ case '_ALLOCATE_AVAILABLE':
324
+ return generateAllocateAvailableCall(callExpr, ctx)
325
+
326
+ case '_ELMCOUNT': {
327
+ // Emit the size of the dimension in place of the dimension name
328
+ const dimArg = callExpr.args[0]
329
+ if (dimArg.kind !== 'variable-ref') {
330
+ throw new Error('Argument for ELMCOUNT must be a dimension name')
331
+ }
332
+ const dimId = dimArg.varId
333
+ return `${sub(dimId).size}`
334
+ }
335
+
336
+ case '_GET_DIRECT_CONSTANTS':
337
+ case '_GET_DIRECT_DATA':
338
+ case '_GET_DIRECT_LOOKUPS':
339
+ // These functions are handled at a higher level, so we should not get here
340
+ throw new Error(`Unexpected function '${fnId}' in code gen for '${ctx.variable.modelLHS}'`)
341
+
342
+ case '_INITIAL':
343
+ // In init mode, only emit the initial expression without the INITIAL function call
344
+ if (ctx.mode.startsWith('init')) {
345
+ return generateExpr(callExpr.args[0], ctx)
346
+ } else {
347
+ throw new Error(`Invalid code gen mode '${ctx.mode}' for variable '${ctx.variable.modelLHS}' with INITIAL`)
348
+ }
349
+
350
+ default: {
351
+ // See if the function name is actually the name of a lookup variable. (See comment
352
+ // 'lookup-call' case above about why this is needed.) Note that if we reach this
353
+ // point and the function call is actually a lookup call, then we can assume that the
354
+ // lookup variable reference does not include subscripts, and we can use the base
355
+ // variable ID only.
356
+ const varId = fnId.toLowerCase()
357
+ const v = Model.varWithName(varId)
358
+ if (v?.isLookup()) {
359
+ // Transform to a `_LOOKUP` function call
360
+ const lookupVarRef = {
361
+ kind: 'variable-ref',
362
+ varName: callExpr.fnName,
363
+ varId
364
+ }
365
+ return generateLookupCall(lookupVarRef, callExpr.args[0], ctx)
366
+ } else {
367
+ // Throw an error if the function is not yet implemented in SDE
368
+ // TODO: This will report false positives in the case of user-defined macros. For now
369
+ // we provide the ability to turn off this check via an environment variable, but we
370
+ // should consider providing a way for the user to declare the names of any user-defined
371
+ // macros so that we can skip this check when those macros are detected.
372
+ if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS !== '0') {
373
+ const msg = `Unhandled function '${fnId}' in code gen for '${ctx.variable.modelLHS}'`
374
+ if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS === 'warn') {
375
+ console.warn(`WARNING: ${msg}`)
376
+ } else {
377
+ throw new Error(msg)
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Generate C code for the given level variable at init time.
387
+ *
388
+ * TODO: Types
389
+ *
390
+ * @param {*} callExpr The function call expression from the parsed model.
391
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
392
+ * @return {string} The generated C code.
393
+ */
394
+ function generateLevelInit(callExpr, ctx) {
395
+ const fnId = callExpr.fnId
396
+
397
+ // Get the index of the argument holding the initial value expression
398
+ let initialArgIndex = 0
399
+ switch (fnId) {
400
+ case '_ACTIVE_INITIAL':
401
+ case '_INTEG':
402
+ initialArgIndex = 1
403
+ break
404
+ case '_DELAY_FIXED': {
405
+ // Emit the code that initializes the `FixedDelay` support struct
406
+ const fixedDelay = ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName)
407
+ const delayArg = generateExpr(callExpr.args[1], ctx)
408
+ const initArg = generateExpr(callExpr.args[2], ctx)
409
+ ctx.emitPostFormula(`${fixedDelay} = __new_fixed_delay(${fixedDelay}, ${delayArg}, ${initArg});`)
410
+ initialArgIndex = 2
411
+ break
412
+ }
413
+ case '_SAMPLE_IF_TRUE':
414
+ initialArgIndex = 2
415
+ break
416
+ case '_DEPRECIATE_STRAIGHTLINE': {
417
+ // Emit the code that initializes the `FixedDelay` support struct
418
+ const dep = ctx.cVarRefWithLhsSubscripts(ctx.variable.depreciationVarName)
419
+ const dtimeArg = generateExpr(callExpr.args[1], ctx)
420
+ const initArg = generateExpr(callExpr.args[3], ctx)
421
+ ctx.emitPostFormula(`${dep} = __new_depreciation(${dep}, ${dtimeArg}, ${initArg});`)
422
+ initialArgIndex = 3
423
+ break
424
+ }
425
+ default:
426
+ throw new Error(`Unhandled function '${fnId}' in code gen for level variable '${ctx.variable.modelLHS}'`)
427
+ }
428
+
429
+ // Emit the initial value expression
430
+ const initialArg = callExpr.args[initialArgIndex]
431
+ return generateExpr(initialArg, ctx)
432
+ }
433
+
434
+ /**
435
+ * Generate C code for the given level variable at eval time.
436
+ *
437
+ * TODO: Types
438
+ *
439
+ * @param {*} callExpr The function call expression from the parsed model.
440
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
441
+ * @return {string} The generated C code.
442
+ */
443
+ function generateLevelEval(callExpr, ctx) {
444
+ const fnId = callExpr.fnId
445
+
446
+ function generateCall(args) {
447
+ return `${fnId}(${args.join(', ')})`
448
+ }
449
+
450
+ switch (fnId) {
451
+ case '_ACTIVE_INITIAL':
452
+ // For ACTIVE INITIAL, emit the first arg without a function call
453
+ return generateExpr(callExpr.args[0], ctx)
454
+
455
+ case '_DELAY_FIXED': {
456
+ // For DELAY FIXED, emit the first arg followed by the FixedDelay support var
457
+ const args = []
458
+ args.push(generateExpr(callExpr.args[0], ctx))
459
+ args.push(ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName))
460
+ return generateCall(args)
461
+ }
462
+
463
+ case '_DEPRECIATE_STRAIGHTLINE': {
464
+ // For DEPRECIATE STRAIGHTLINE, emit the first arg followed by the Depreciation support var
465
+ const args = []
466
+ args.push(generateExpr(callExpr.args[0], ctx))
467
+ args.push(ctx.cVarRefWithLhsSubscripts(ctx.variable.depreciationVarName))
468
+ return generateCall(args)
469
+ }
470
+
471
+ case '_INTEG':
472
+ case '_SAMPLE_IF_TRUE': {
473
+ // At eval time, emit the variable LHS as the first arg, giving the current value for the level.
474
+ // Then emit the remaining arguments.
475
+ const args = []
476
+ args.push(ctx.cLhs)
477
+ args.push(generateExpr(callExpr.args[0], ctx))
478
+ if (fnId === '_SAMPLE_IF_TRUE') {
479
+ args.push(generateExpr(callExpr.args[1], ctx))
480
+ }
481
+ return generateCall(args)
482
+ }
483
+
484
+ default:
485
+ throw new Error(`Unhandled function '${fnId}' in code gen for level variable '${ctx.variable.modelLHS}'`)
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Generate C code for a lookup call.
491
+ *
492
+ * TODO: Types
493
+ *
494
+ * @param {*} lookupVarRef The lookup `VariableRef`.
495
+ * @param {*} argExpr The parsed `Expr` for the single argument for the lookup.
496
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
497
+ * @return {string} The generated C code.
498
+ */
499
+ function generateLookupCall(lookupVarRef, argExpr, ctx) {
500
+ const cVarRef = ctx.cVarRef(lookupVarRef)
501
+ const cArg = generateExpr(argExpr, ctx)
502
+ return `_LOOKUP(${cVarRef}, ${cArg})`
503
+ }
504
+
505
+ /**
506
+ * Generate C code for an array function call (e.g., `SUM`).
507
+ *
508
+ * @param {*} callExpr The function call expression from the parsed model.
509
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
510
+ * @return {string} The generated C code.
511
+ */
512
+ function generateArrayFunctionCall(callExpr, ctx) {
513
+ // Determine the initial value and loop body depending on the function
514
+ let tmpVar
515
+ let initValue
516
+ let loopBodyOp
517
+ let returnCode
518
+ let vsCondVar
519
+ switch (callExpr.fnId) {
520
+ case '_SUM':
521
+ initValue = '0.0'
522
+ loopBodyOp = 'sum'
523
+ break
524
+
525
+ case '_VMIN':
526
+ initValue = 'DBL_MAX'
527
+ loopBodyOp = 'min'
528
+ break
529
+
530
+ case '_VMAX':
531
+ initValue = '-DBL_MAX'
532
+ loopBodyOp = 'max'
533
+ break
534
+
535
+ case '_VECTOR_SELECT': {
536
+ // For `VECTOR SELECT`, we emit different inner loop code depending on the `numerical_action`
537
+ // and `missing_vals` arguments, so extract those here first
538
+ // TODO: We should also implement handling of the `error_action` argument
539
+
540
+ const constantValue = argExpr => {
541
+ // TODO: We can handle more complex expressions here if necessary if we used `reduceExpr`
542
+ switch (argExpr.kind) {
543
+ case 'number':
544
+ return argExpr.value
545
+ case 'keyword':
546
+ if (argExpr.text === ':NA:') {
547
+ return '_NA_'
548
+ } else {
549
+ throw new Error(`Unhandled keyword ${argExpr.text} in VECTOR SELECT argument`)
550
+ }
551
+ case 'variable-ref': {
552
+ // TODO: This won't work for subscripted variable references; should fix this
553
+ const variable = Model.varWithName(argExpr.varId)
554
+ if (variable && variable.varType === 'const') {
555
+ return variable.parsedEqn.rhs.expr.value
556
+ } else {
557
+ throw new Error(`Failed to resolve variable '${argExpr.varName}' for VECTOR SELECT argument`)
558
+ }
559
+ }
560
+ default:
561
+ throw new Error('The argument for VECTOR SELECT must resolve to a constant')
562
+ }
563
+ }
564
+
565
+ const missingValsArg = constantValue(callExpr.args[2])
566
+ const missingValsCode = missingValsArg === '_NA_' ? '_NA_' : cdbl(missingValsArg)
567
+
568
+ // TODO: Handle other actions
569
+ const numericalActionArg = constantValue(callExpr.args[3])
570
+ switch (numericalActionArg) {
571
+ case 0:
572
+ initValue = '0.0'
573
+ loopBodyOp = 'sum'
574
+ break
575
+ case 3:
576
+ initValue = '-DBL_MAX'
577
+ loopBodyOp = 'max'
578
+ break
579
+ default:
580
+ throw new Error(`Unsupported numerical_action value (${numericalActionArg}) for VECTOR SELECT`)
581
+ }
582
+
583
+ // Emit the temporary condition variable declaration
584
+ vsCondVar = newTmpVarName()
585
+ ctx.emitPreFormula(` bool ${vsCondVar} = false;`)
586
+
587
+ // Define the code that will be emitted in place of the `VECTOR SELECT` call
588
+ tmpVar = newTmpVarName()
589
+ returnCode = `${vsCondVar} ? ${tmpVar} : ${missingValsCode}`
590
+ break
591
+ }
592
+
593
+ default:
594
+ throw new Error(`Unexpected function call '${callExpr.fnId}' when reading '${ctx.variable.modelLHS}'`)
595
+ }
596
+
597
+ // Emit the temporary variable declaration
598
+ if (!tmpVar) {
599
+ tmpVar = newTmpVarName()
600
+ }
601
+ ctx.emitPreFormula(` double ${tmpVar} = ${initValue};`)
602
+
603
+ // Find all marked dimensions used in the array function arguments
604
+ const markedDimIds = new Set()
605
+ for (const argExpr of callExpr.args) {
606
+ visitVariableRefs(argExpr, varRef => {
607
+ if (varRef.subscriptRefs) {
608
+ const subIds = varRef.subscriptRefs.map(subRef => subRef.subId)
609
+ extractMarkedDims(subIds).forEach(dimId => markedDimIds.add(dimId))
610
+ }
611
+ })
612
+ }
613
+
614
+ // Open the array function loop(s)
615
+ for (const markedDimId of markedDimIds) {
616
+ ctx.addMarkedDim(markedDimId)
617
+ const n = sub(markedDimId).size
618
+ const i = ctx.arrayIndexVars.index(markedDimId)
619
+ ctx.emitPreFormula(` for (size_t ${i} = 0; ${i} < ${n}; ${i}++) {`)
620
+ }
621
+
622
+ // Emit the body of the array function loop. Note that we generate the expression code here
623
+ // only after resolving the marked dimensions because the code that generates variable references
624
+ // needs to take the marked dimension state into account.
625
+ function innerStmt(argCode) {
626
+ switch (loopBodyOp) {
627
+ case 'sum':
628
+ return `${tmpVar} += ${argCode};`
629
+ case 'min':
630
+ return `${tmpVar} = fmin(${tmpVar}, ${argCode});`
631
+ case 'max':
632
+ return `${tmpVar} = fmax(${tmpVar}, ${argCode});`
633
+ default:
634
+ throw new Error(`Unexpected loop body op ${loopBodyOp} for VECTOR SELECT`)
635
+ }
636
+ }
637
+
638
+ if (callExpr.fnId === '_VECTOR_SELECT') {
639
+ // For `VECTOR SELECT`, the inner loop includes a conditional
640
+ const selArrayCode = generateExpr(callExpr.args[0], ctx)
641
+ const exprArrayCode = generateExpr(callExpr.args[1], ctx)
642
+ ctx.emitPreFormula(` if (bool_cond(${selArrayCode})) {`)
643
+ ctx.emitPreFormula(` ${innerStmt(exprArrayCode)}`)
644
+ ctx.emitPreFormula(` ${vsCondVar} = true;`)
645
+ ctx.emitPreFormula(' }')
646
+ } else {
647
+ // For other functions, the inner loop is a simple statement
648
+ const argCode = generateExpr(callExpr.args[0], ctx)
649
+ ctx.emitPreFormula(` ${innerStmt(argCode)}`)
650
+ }
651
+
652
+ // Close the array function loop(s)
653
+ for (let i = 0; i < markedDimIds.size; i++) {
654
+ ctx.emitPreFormula(` }`)
655
+ }
656
+
657
+ // Reset marked dim state
658
+ ctx.resetMarkedDims()
659
+
660
+ if (returnCode) {
661
+ // Emit the expression defined above in place of the array function
662
+ return returnCode
663
+ } else {
664
+ // Emit the temporary variable into the expression in place of the array function call
665
+ return tmpVar
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Generate C code for a `VECTOR ELM MAP` function call.
671
+ *
672
+ * @param {*} callExpr The function call expression from the parsed model.
673
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
674
+ * @return {string} The generated C code.
675
+ */
676
+ function generateVectorElmMapCall(callExpr, ctx) {
677
+ function validateArg(index, name) {
678
+ const arg = callExpr.args[index]
679
+ if (arg.kind === 'variable-ref') {
680
+ return arg
681
+ } else {
682
+ throw new Error(
683
+ `VECTOR ELM MAP argument '${name}' must be a variable reference in code gen for '${ctx.variable.modelLHS}'`
684
+ )
685
+ }
686
+ }
687
+
688
+ // Process the vector argument
689
+ const vecArg = validateArg(0, 'vec')
690
+ let vecVarRefId = vecArg.varId
691
+ const vecSubIds = vecArg.subscriptRefs.map(subRef => subRef.subId)
692
+
693
+ // The marked dimension is an index in the vector argument
694
+ let subFamily
695
+ let subBase
696
+ for (let subId of vecSubIds) {
697
+ if (isIndex(subId)) {
698
+ const index = sub(subId)
699
+ subFamily = index.family
700
+ subBase = index.value
701
+ break
702
+ }
703
+ }
704
+ if (subFamily === undefined) {
705
+ throw new Error(`Failed to resolve index for VECTOR ELM MAP call in code gen for '${ctx.variable.modelLHS}'`)
706
+ }
707
+
708
+ // Process the offset argument
709
+ const offsetArgCode = generateExpr(callExpr.args[1], ctx)
710
+
711
+ // The `VECTOR ELM MAP` function replaces one subscript with a calculated offset from
712
+ // a base index
713
+ const rhsSubIds = normalizeSubscripts(vecSubIds)
714
+ const cSubscripts = rhsSubIds.map(rhsSubId => {
715
+ if (isIndex(rhsSubId)) {
716
+ return `[${subFamily}[(size_t)(${subBase} + ${offsetArgCode})]]`
717
+ } else {
718
+ const subIndex = ctx.loopIndexVars.index(rhsSubId)
719
+ return `[${rhsSubId}[${subIndex}]]`
720
+ }
721
+ })
722
+
723
+ // Generate the RHS expression
724
+ return `${vecVarRefId}${cSubscripts.join('')}`
725
+ }
726
+
727
+ /**
728
+ * Generate C code for a `VECTOR SORT ORDER` function call.
729
+ *
730
+ * @param {*} callExpr The function call expression from the parsed model.
731
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
732
+ * @return {string} The generated C code.
733
+ */
734
+ function generateVectorSortOrderCall(callExpr, ctx) {
735
+ // Process the vector argument
736
+ const vecArg = callExpr.args[0]
737
+ if (vecArg.kind !== 'variable-ref') {
738
+ throw new Error(`VECTOR SORT ORDER argument 'vec' must be a variable reference`)
739
+ }
740
+ let vecVarRefId = vecArg.varId
741
+ const vecSubIds = vecArg.subscriptRefs.map(subRef => subRef.subId)
742
+
743
+ // Process the sort direction argument
744
+ const dirArg = generateExpr(callExpr.args[1], ctx)
745
+
746
+ // The `VECTOR SORT ORDER` function iterates over the last subscript in the vector
747
+ // argument, so determine the position of that subscript
748
+ let dimId = vecSubIds[0]
749
+ let subIndex = ctx.loopIndexVars.index(dimId)
750
+ if (vecSubIds.length > 1) {
751
+ // TODO: This code was from the old EquationGen; it seems to only handle the case of 2
752
+ // dimensions, but what about other cases?
753
+ vecVarRefId += `[${vecSubIds[0]}[${subIndex}]]`
754
+ dimId = vecSubIds[1]
755
+ subIndex = ctx.loopIndexVars.index(dimId)
756
+ }
757
+
758
+ // Generate the code that is emitted before the entire block (before any loops are opened)
759
+ const tmpVarId = newTmpVarName()
760
+ const dimSize = sub(dimId).size
761
+ ctx.emitPreInnerLoop(` double* ${tmpVarId} = _VECTOR_SORT_ORDER(${vecVarRefId}, ${dimSize}, ${dirArg});`)
762
+
763
+ // Generate the RHS expression used in the inner loop
764
+ return `${tmpVarId}[${dimId}[${subIndex}]]`
765
+ }
766
+
767
+ /**
768
+ * Generate C code for an `ALLOCATE AVAILABLE` function call.
769
+ *
770
+ * @param {*} callExpr The function call expression from the parsed model.
771
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
772
+ * @return {string} The generated C code.
773
+ */
774
+ function generateAllocateAvailableCall(callExpr, ctx) {
775
+ function validateArg(index, name) {
776
+ const arg = callExpr.args[index]
777
+ if (arg.kind === 'variable-ref') {
778
+ return arg
779
+ } else {
780
+ throw new Error(`ALLOCATE AVAILABLE argument '${name}' must be a variable reference`)
781
+ }
782
+ }
783
+
784
+ // Process the request argument
785
+ const reqArg = validateArg(0, 'req')
786
+ const reqRefId = reqArg.varId
787
+ const reqSubIds = reqArg.subscriptRefs.map(subRef => subRef.subId)
788
+
789
+ // Process the priority argument
790
+ const priorityArg = validateArg(1, 'priority')
791
+ const priorityRefId = priorityArg.varId
792
+
793
+ // Process the avail argument
794
+ const availArg = validateArg(2, 'avail')
795
+ const availRefId = availArg.varId
796
+
797
+ // The `ALLOCATE AVAILABLE` function iterates over the subscript in its first arg
798
+ const dimId = reqSubIds[0]
799
+ const subIndex = ctx.loopIndexVars.index(dimId)
800
+
801
+ // Generate the code that is emitted before the entire block (before any loops are opened)
802
+ const tmpVarId = newTmpVarName()
803
+ const dimSize = sub(dimId).size
804
+ ctx.emitPreInnerLoop(
805
+ ` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${reqRefId}, (double*)${priorityRefId}, ${availRefId}, ${dimSize});`
806
+ )
807
+
808
+ // Generate the RHS expression used in the inner loop
809
+ return `${tmpVarId}[${dimId}[${subIndex}]]`
810
+ }
811
+
812
+ /**
813
+ * Recursively traverse the given expression and call the function when visiting a variable ref.
814
+ *
815
+ * @param {*} expr The expression to visit.
816
+ * @param {*} onVarRef The function that is called when encountering a variable ref.
817
+ */
818
+ function visitVariableRefs(expr, onVarRef) {
819
+ switch (expr.kind) {
820
+ case 'number':
821
+ case 'string':
822
+ case 'keyword':
823
+ case 'lookup-def':
824
+ break
825
+
826
+ case 'variable-ref':
827
+ onVarRef(expr)
828
+ break
829
+
830
+ case 'parens':
831
+ case 'unary-op':
832
+ visitVariableRefs(expr.expr, onVarRef)
833
+ break
834
+
835
+ case 'binary-op':
836
+ visitVariableRefs(expr.lhs, onVarRef)
837
+ visitVariableRefs(expr.rhs, onVarRef)
838
+ break
839
+
840
+ case 'lookup-call':
841
+ visitVariableRefs(expr.arg, onVarRef)
842
+ break
843
+
844
+ case 'function-call':
845
+ expr.args.forEach(arg => visitVariableRefs(arg, onVarRef))
846
+ break
847
+
848
+ default:
849
+ throw new Error(`Unhandled expression kind '${expr.kind}' in visitVariableRefs`)
850
+ }
851
+ }