@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.
- package/package.json +2 -1
- package/src/_shared/helpers.js +0 -4
- package/src/_shared/read-dat.js +11 -2
- package/src/generate/code-gen.js +51 -59
- package/src/generate/direct-data-helpers.js +86 -0
- package/src/generate/equation-gen.js +1 -1
- package/src/generate/expand-var-names.js +115 -0
- package/src/generate/gen-const-list.js +89 -0
- package/src/generate/gen-direct-const.js +91 -0
- package/src/generate/gen-equation.js +273 -0
- package/src/generate/gen-expr.js +851 -0
- package/src/generate/gen-lookup-from-direct.js +105 -0
- package/src/generate/gen-lookup-from-external.js +115 -0
- package/src/generate/gen-lookup-from-points.js +85 -0
- package/src/model/equation-reader.js +27 -22
- package/src/model/model.js +87 -36
- package/src/model/read-equation-fn-delay.js +312 -0
- package/src/model/read-equation-fn-npv.js +67 -0
- package/src/model/read-equation-fn-smooth.js +133 -0
- package/src/model/read-equation-fn-trend.js +44 -0
- package/src/model/read-equation-fn-with-lookup.js +37 -0
- package/src/model/read-equations.js +795 -0
- package/src/model/read-subscripts.js +77 -0
- package/src/model/read-variables.js +264 -0
- package/src/model/reduce-variables.js +166 -0
- package/src/model/toposort.js +1 -1
- package/src/model/variable.js +7 -2
- package/src/parse-and-generate.js +65 -12
|
@@ -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
|
+
}
|