@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.
@@ -30,10 +30,11 @@ import LoopIndexVars from './loop-index-vars.js'
30
30
  * @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA` call (e.g.,
31
31
  * `?data`) to the tabular data contained in the loaded data file.
32
32
  * @param {string} modelDir The path to the directory containing the model (used for resolving data files).
33
+ * @param {'c' | 'js'} outFormat The output format.
33
34
  * @return {string[]} An array of strings containing the generated C code for the variable,
34
35
  * one string per line of code.
35
36
  */
36
- export function generateEquation(variable, mode, extData, directData, modelDir) {
37
+ export function generateEquation(variable, mode, extData, directData, modelDir, outFormat) {
37
38
  // Maps of LHS subscript families to loop index vars for lookup on the RHS
38
39
  const loopIndexVars = new LoopIndexVars(['i', 'j', 'k', 'l', 'm'])
39
40
  const arrayIndexVars = new LoopIndexVars(['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r'])
@@ -68,12 +69,13 @@ export function generateEquation(variable, mode, extData, directData, modelDir)
68
69
 
69
70
  // Turn each dimension ID into a loop with a loop index variable.
70
71
  // If the variable has no subscripts, nothing will be emitted here.
72
+ const indexDecl = outFormat === 'js' ? 'let' : 'size_t'
71
73
  const openLoops = []
72
74
  const closeLoops = []
73
75
  for (const dimId of dimIds) {
74
76
  const indexName = loopIndexVars.index(dimId)
75
77
  const dimLength = sub(dimId).size
76
- openLoops.push(` for (size_t ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
78
+ openLoops.push(` for (${indexDecl} ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
77
79
  closeLoops.push(' }')
78
80
  }
79
81
 
@@ -86,7 +88,7 @@ export function generateEquation(variable, mode, extData, directData, modelDir)
86
88
  // The variable already has data points defined, so generate a new lookup using that data.
87
89
  // Note that unlike the other lookup cases, this one needs to include loop open/close code
88
90
  // if the variable is subscripted.
89
- const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ true, cLhs, loopIndexVars)
91
+ const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ true, cLhs, loopIndexVars, outFormat)
90
92
  if (lookupDef.length > 0) {
91
93
  return [comment, ...openLoops, ...lookupDef, ...closeLoops]
92
94
  } else {
@@ -95,18 +97,25 @@ export function generateEquation(variable, mode, extData, directData, modelDir)
95
97
  } else if (variable.directDataArgs) {
96
98
  // The data is referenced using a `GET DIRECT DATA` call; generate one or more lookups
97
99
  // using the data defined in external files
98
- return generateLookupsFromDirectData(variable, mode, directData, modelDir, cLhs)
100
+ return generateLookupsFromDirectData(variable, mode, directData, modelDir, cLhs, outFormat)
99
101
  } else {
100
102
  // This is a "normal" data variable; generate one or more lookups using the data defined
101
103
  // in external files
102
- return generateLookupsFromExternalData(variable, mode, extData, cLhs)
104
+ return generateLookupsFromExternalData(variable, mode, extData, cLhs, outFormat)
103
105
  }
104
106
  }
105
107
 
106
108
  // Apply special handling for lookup variables. The data for lookup variables is already
107
109
  // defined as a set of explicit data points (stored in the `Variable` instance).
108
110
  if (variable.isLookup()) {
109
- return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars)
111
+ if (variable.varSubtype === 'gameInputs') {
112
+ // For a synthesized game inputs lookup, there is no data array (the data is expected
113
+ // to be supplied at runtime), so don't emit decl or init code for these
114
+ return []
115
+ } else {
116
+ // For all other lookups, emit decl/init code for the lookup
117
+ return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
118
+ }
110
119
  }
111
120
 
112
121
  // Keep a buffer of code that will be included before the innermost loop
@@ -125,6 +134,7 @@ export function generateEquation(variable, mode, extData, directData, modelDir)
125
134
  const genExprCtx = {
126
135
  variable,
127
136
  mode,
137
+ outFormat,
128
138
  cLhs,
129
139
  loopIndexVars,
130
140
  arrayIndexVars,
@@ -8,7 +8,8 @@ import Model from '../model/model.js'
8
8
  *
9
9
  * @param {*} variable The `Variable` instance to process.
10
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.
11
+ * @param {'c' | 'js'} outFormat The output format.
12
+ * @param {string} cLhs The C/JS code for the LHS variable reference.
12
13
  * @param {LoopIndexVars} loopIndexVars The loop index state used for LHS dimensions.
13
14
  * @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions (that use marked dimensions).
14
15
  * @param {() => void} resetMarkedDims Function that resets the marked dimension state.
@@ -23,7 +24,7 @@ import Model from '../model/model.js'
23
24
  * referenced in a RHS expression.
24
25
  * @param {(baseVarId: string) => string} cVarRefWithLhsSubscripts Function that returns a C variable reference that
25
26
  * 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
+ * @param {(subOrDimId: string) => string} cVarIndex Function that returns C/JS code for indexing into a subscripted variable.
27
28
  */
28
29
 
29
30
  /**
@@ -33,7 +34,7 @@ import Model from '../model/model.js'
33
34
  *
34
35
  * @param {*} expr The expression from the parsed model.
35
36
  * @param {GenExprContext} ctx The context used when generating code for the expression.
36
- * @return {string} The generated C code.
37
+ * @return {string} The generated C/JS code.
37
38
  */
38
39
  export function generateExpr(expr, ctx) {
39
40
  switch (expr.kind) {
@@ -57,7 +58,7 @@ export function generateExpr(expr, ctx) {
57
58
  // This is a reference to a known variable
58
59
  if (v.isData()) {
59
60
  // It's a data variable; transform to a `_LOOKUP` function call
60
- return `_LOOKUP(${ctx.cVarRef(expr)}, _time)`
61
+ return `${fnRef('_LOOKUP', ctx)}(${ctx.cVarRef(expr)}, _time)`
61
62
  } else {
62
63
  // It's not a data variable; generate a normal variable reference
63
64
  return ctx.cVarRef(expr)
@@ -102,15 +103,15 @@ export function generateExpr(expr, ctx) {
102
103
  const lhs = generateExpr(expr.lhs, ctx)
103
104
  const rhs = generateExpr(expr.rhs, ctx)
104
105
  if (expr.op === '^') {
105
- return `pow(${lhs}, ${rhs})`
106
+ return `${ctx.outFormat === 'js' ? 'fns.POW' : 'pow'}(${lhs}, ${rhs})`
106
107
  } else {
107
108
  let op
108
109
  switch (expr.op) {
109
110
  case '=':
110
- op = '=='
111
+ op = ctx.outFormat === 'js' ? '===' : '=='
111
112
  break
112
113
  case '<>':
113
- op = '!='
114
+ op = ctx.outFormat === 'js' ? '!==' : '!='
114
115
  break
115
116
  case ':AND:':
116
117
  op = '&&'
@@ -154,13 +155,13 @@ export function generateExpr(expr, ctx) {
154
155
  }
155
156
 
156
157
  /**
157
- * Generate C code for the given function call.
158
+ * Generate C/JS code for the given function call.
158
159
  *
159
160
  * TODO: Types
160
161
  *
161
162
  * @param {*} callExpr The function call expression from the parsed model.
162
163
  * @param {GenExprContext} ctx The context used when generating code for the expression.
163
- * @return {string} The generated C code.
164
+ * @return {string} The generated C/JS code.
164
165
  */
165
166
  function generateFunctionCall(callExpr, ctx) {
166
167
  const fnId = callExpr.fnId
@@ -170,8 +171,8 @@ function generateFunctionCall(callExpr, ctx) {
170
171
  //
171
172
  // Simple functions
172
173
  //
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.
174
+ // Each of these functions is implemented with a C/JS function or C macro, so no further processing
175
+ // is required other than to emit the function/macro call.
175
176
  //
176
177
  //
177
178
 
@@ -181,7 +182,6 @@ function generateFunctionCall(callExpr, ctx) {
181
182
  case '_ARCTAN':
182
183
  case '_COS':
183
184
  case '_EXP':
184
- case '_GAME':
185
185
  case '_GAMMA_LN':
186
186
  case '_IF_THEN_ELSE':
187
187
  case '_INTEGER':
@@ -202,16 +202,26 @@ function generateFunctionCall(callExpr, ctx) {
202
202
  case '_WITH_LOOKUP':
203
203
  case '_XIDZ':
204
204
  case '_ZIDZ': {
205
- // For simple functions, emit a C function call with a generated C expression for each argument
206
205
  const args = callExpr.args.map(argExpr => generateExpr(argExpr, ctx))
207
- return `${fnId}(${args.join(', ')})`
206
+ if (ctx.outFormat === 'js' && fnId === '_GAMMA_LN') {
207
+ throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`)
208
+ }
209
+ if (ctx.outFormat === 'js' && fnId === '_IF_THEN_ELSE') {
210
+ // When generating conditional expressions for JS target, since we can't rely on macros like we do for C,
211
+ // it is better to translate it into a ternary instead of relying on a built-in function (since the latter
212
+ // would require always evaluating both branches, while the former can be more optimized by the interpreter)
213
+ return `((${args[0]}) ? (${args[1]}) : (${args[2]}))`
214
+ } else {
215
+ // For simple functions, emit a C/JS function call with a generated C/JS expression for each argument
216
+ return `${fnRef(fnId, ctx)}(${args.join(', ')})`
217
+ }
208
218
  }
209
219
 
210
220
  //
211
221
  //
212
222
  // Lookup functions
213
223
  //
214
- // Each of these functions is implemented with a C function (like the simple functions above),
224
+ // Each of these functions is implemented with a C/JS function (like the simple functions above),
215
225
  // but we need to handle the first argument specially, otherwise we would get the default handling
216
226
  // for data variables, which generates a lookup call (see 'variable-ref' case in `generateExpr`).
217
227
  //
@@ -222,10 +232,18 @@ function generateFunctionCall(callExpr, ctx) {
222
232
  case '_LOOKUP_FORWARD':
223
233
  case '_LOOKUP_INVERT': {
224
234
  // 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.
235
+ // a C/JS function call with a generated C/JS expression for each remaining argument.
226
236
  const cVarRef = ctx.cVarRef(callExpr.args[0])
227
237
  const cArgs = callExpr.args.slice(1).map(arg => generateExpr(arg, ctx))
228
- return `${fnId}(${cVarRef}, ${cArgs.join(', ')})`
238
+ return `${fnRef(fnId, ctx)}(${cVarRef}, ${cArgs.join(', ')})`
239
+ }
240
+
241
+ case '_GAME': {
242
+ // For the GAME function, emit a C/JS function call that has the synthesized game inputs lookup
243
+ // as the first argument, followed by the default value argument from the function call
244
+ const cLookupArg = ctx.cVarRefWithLhsSubscripts(ctx.variable.gameLookupVarName)
245
+ const cDefaultArg = generateExpr(callExpr.args[0], ctx)
246
+ return `${fnRef(fnId, ctx)}(${cLookupArg}, ${cDefaultArg})`
229
247
  }
230
248
 
231
249
  //
@@ -240,6 +258,9 @@ function generateFunctionCall(callExpr, ctx) {
240
258
  case '_SAMPLE_IF_TRUE':
241
259
  case '_INTEG':
242
260
  // Split level functions into init and eval expressions
261
+ if (ctx.outFormat === 'js' && (fnId === '_DELAY_FIXED' || fnId === '_DEPRECIATE_STRAIGHTLINE')) {
262
+ throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`)
263
+ }
243
264
  if (ctx.mode.startsWith('init')) {
244
265
  return generateLevelInit(callExpr, ctx)
245
266
  } else if (ctx.mode === 'eval') {
@@ -321,6 +342,9 @@ function generateFunctionCall(callExpr, ctx) {
321
342
  //
322
343
 
323
344
  case '_ALLOCATE_AVAILABLE':
345
+ if (ctx.outFormat === 'js') {
346
+ throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`)
347
+ }
324
348
  return generateAllocateAvailableCall(callExpr, ctx)
325
349
 
326
350
  case '_ELMCOUNT': {
@@ -383,13 +407,13 @@ function generateFunctionCall(callExpr, ctx) {
383
407
  }
384
408
 
385
409
  /**
386
- * Generate C code for the given level variable at init time.
410
+ * Generate C/JS code for the given level variable at init time.
387
411
  *
388
412
  * TODO: Types
389
413
  *
390
414
  * @param {*} callExpr The function call expression from the parsed model.
391
415
  * @param {GenExprContext} ctx The context used when generating code for the expression.
392
- * @return {string} The generated C code.
416
+ * @return {string} The generated C/JS code.
393
417
  */
394
418
  function generateLevelInit(callExpr, ctx) {
395
419
  const fnId = callExpr.fnId
@@ -432,19 +456,19 @@ function generateLevelInit(callExpr, ctx) {
432
456
  }
433
457
 
434
458
  /**
435
- * Generate C code for the given level variable at eval time.
459
+ * Generate C/JS code for the given level variable at eval time.
436
460
  *
437
461
  * TODO: Types
438
462
  *
439
463
  * @param {*} callExpr The function call expression from the parsed model.
440
464
  * @param {GenExprContext} ctx The context used when generating code for the expression.
441
- * @return {string} The generated C code.
465
+ * @return {string} The generated C/JS code.
442
466
  */
443
467
  function generateLevelEval(callExpr, ctx) {
444
468
  const fnId = callExpr.fnId
445
469
 
446
470
  function generateCall(args) {
447
- return `${fnId}(${args.join(', ')})`
471
+ return `${fnRef(fnId, ctx)}(${args.join(', ')})`
448
472
  }
449
473
 
450
474
  switch (fnId) {
@@ -478,7 +502,15 @@ function generateLevelEval(callExpr, ctx) {
478
502
  if (fnId === '_SAMPLE_IF_TRUE') {
479
503
  args.push(generateExpr(callExpr.args[1], ctx))
480
504
  }
481
- return generateCall(args)
505
+ if (ctx.outFormat === 'js' && fnId === '_SAMPLE_IF_TRUE') {
506
+ // When generating conditional expressions for JS target, since we can't rely on macros like we do for C,
507
+ // it is better to translate it into a ternary instead of relying on a built-in function (since the latter
508
+ // would require always evaluating both branches, while the former can be more optimized by the interpreter)
509
+ return `((${args[1]}) ? (${args[2]}) : (${args[0]}))`
510
+ } else {
511
+ // In all other cases, generate a normal call
512
+ return generateCall(args)
513
+ }
482
514
  }
483
515
 
484
516
  default:
@@ -487,27 +519,27 @@ function generateLevelEval(callExpr, ctx) {
487
519
  }
488
520
 
489
521
  /**
490
- * Generate C code for a lookup call.
522
+ * Generate C/JS code for a lookup call.
491
523
  *
492
524
  * TODO: Types
493
525
  *
494
526
  * @param {*} lookupVarRef The lookup `VariableRef`.
495
527
  * @param {*} argExpr The parsed `Expr` for the single argument for the lookup.
496
528
  * @param {GenExprContext} ctx The context used when generating code for the expression.
497
- * @return {string} The generated C code.
529
+ * @return {string} The generated C/JS code.
498
530
  */
499
531
  function generateLookupCall(lookupVarRef, argExpr, ctx) {
500
532
  const cVarRef = ctx.cVarRef(lookupVarRef)
501
533
  const cArg = generateExpr(argExpr, ctx)
502
- return `_LOOKUP(${cVarRef}, ${cArg})`
534
+ return `${fnRef('_LOOKUP', ctx)}(${cVarRef}, ${cArg})`
503
535
  }
504
536
 
505
537
  /**
506
- * Generate C code for an array function call (e.g., `SUM`).
538
+ * Generate C/JS code for an array function call (e.g., `SUM`).
507
539
  *
508
540
  * @param {*} callExpr The function call expression from the parsed model.
509
541
  * @param {GenExprContext} ctx The context used when generating code for the expression.
510
- * @return {string} The generated C code.
542
+ * @return {string} The generated C/JS code.
511
543
  */
512
544
  function generateArrayFunctionCall(callExpr, ctx) {
513
545
  // Determine the initial value and loop body depending on the function
@@ -523,12 +555,12 @@ function generateArrayFunctionCall(callExpr, ctx) {
523
555
  break
524
556
 
525
557
  case '_VMIN':
526
- initValue = 'DBL_MAX'
558
+ initValue = maxNumber(ctx)
527
559
  loopBodyOp = 'min'
528
560
  break
529
561
 
530
562
  case '_VMAX':
531
- initValue = '-DBL_MAX'
563
+ initValue = `-${maxNumber(ctx)}`
532
564
  loopBodyOp = 'max'
533
565
  break
534
566
 
@@ -573,7 +605,7 @@ function generateArrayFunctionCall(callExpr, ctx) {
573
605
  loopBodyOp = 'sum'
574
606
  break
575
607
  case 3:
576
- initValue = '-DBL_MAX'
608
+ initValue = `-${maxNumber(ctx)}`
577
609
  loopBodyOp = 'max'
578
610
  break
579
611
  default:
@@ -582,7 +614,7 @@ function generateArrayFunctionCall(callExpr, ctx) {
582
614
 
583
615
  // Emit the temporary condition variable declaration
584
616
  vsCondVar = newTmpVarName()
585
- ctx.emitPreFormula(` bool ${vsCondVar} = false;`)
617
+ ctx.emitPreFormula(` ${varDecl('bool', vsCondVar, 'false', ctx)}`)
586
618
 
587
619
  // Define the code that will be emitted in place of the `VECTOR SELECT` call
588
620
  tmpVar = newTmpVarName()
@@ -598,7 +630,7 @@ function generateArrayFunctionCall(callExpr, ctx) {
598
630
  if (!tmpVar) {
599
631
  tmpVar = newTmpVarName()
600
632
  }
601
- ctx.emitPreFormula(` double ${tmpVar} = ${initValue};`)
633
+ ctx.emitPreFormula(` ${varDecl('double', tmpVar, initValue, ctx)}`)
602
634
 
603
635
  // Find all marked dimensions used in the array function arguments
604
636
  const markedDimIds = new Set()
@@ -612,11 +644,12 @@ function generateArrayFunctionCall(callExpr, ctx) {
612
644
  }
613
645
 
614
646
  // Open the array function loop(s)
647
+ const indexDecl = ctx.outFormat === 'js' ? 'let' : 'size_t'
615
648
  for (const markedDimId of markedDimIds) {
616
649
  ctx.addMarkedDim(markedDimId)
617
650
  const n = sub(markedDimId).size
618
651
  const i = ctx.arrayIndexVars.index(markedDimId)
619
- ctx.emitPreFormula(` for (size_t ${i} = 0; ${i} < ${n}; ${i}++) {`)
652
+ ctx.emitPreFormula(` for (${indexDecl} ${i} = 0; ${i} < ${n}; ${i}++) {`)
620
653
  }
621
654
 
622
655
  // Emit the body of the array function loop. Note that we generate the expression code here
@@ -627,9 +660,9 @@ function generateArrayFunctionCall(callExpr, ctx) {
627
660
  case 'sum':
628
661
  return `${tmpVar} += ${argCode};`
629
662
  case 'min':
630
- return `${tmpVar} = fmin(${tmpVar}, ${argCode});`
663
+ return `${tmpVar} = ${minFunc(ctx)}(${tmpVar}, ${argCode});`
631
664
  case 'max':
632
- return `${tmpVar} = fmax(${tmpVar}, ${argCode});`
665
+ return `${tmpVar} = ${maxFunc(ctx)}(${tmpVar}, ${argCode});`
633
666
  default:
634
667
  throw new Error(`Unexpected loop body op ${loopBodyOp} for VECTOR SELECT`)
635
668
  }
@@ -639,7 +672,11 @@ function generateArrayFunctionCall(callExpr, ctx) {
639
672
  // For `VECTOR SELECT`, the inner loop includes a conditional
640
673
  const selArrayCode = generateExpr(callExpr.args[0], ctx)
641
674
  const exprArrayCode = generateExpr(callExpr.args[1], ctx)
642
- ctx.emitPreFormula(` if (bool_cond(${selArrayCode})) {`)
675
+ if (ctx.outFormat === 'c') {
676
+ ctx.emitPreFormula(` if (bool_cond(${selArrayCode})) {`)
677
+ } else {
678
+ ctx.emitPreFormula(` if (${selArrayCode}) {`)
679
+ }
643
680
  ctx.emitPreFormula(` ${innerStmt(exprArrayCode)}`)
644
681
  ctx.emitPreFormula(` ${vsCondVar} = true;`)
645
682
  ctx.emitPreFormula(' }')
@@ -667,11 +704,11 @@ function generateArrayFunctionCall(callExpr, ctx) {
667
704
  }
668
705
 
669
706
  /**
670
- * Generate C code for a `VECTOR ELM MAP` function call.
707
+ * Generate C/JS code for a `VECTOR ELM MAP` function call.
671
708
  *
672
709
  * @param {*} callExpr The function call expression from the parsed model.
673
710
  * @param {GenExprContext} ctx The context used when generating code for the expression.
674
- * @return {string} The generated C code.
711
+ * @return {string} The generated C/JS code.
675
712
  */
676
713
  function generateVectorElmMapCall(callExpr, ctx) {
677
714
  function validateArg(index, name) {
@@ -713,7 +750,18 @@ function generateVectorElmMapCall(callExpr, ctx) {
713
750
  const rhsSubIds = normalizeSubscripts(vecSubIds)
714
751
  const cSubscripts = rhsSubIds.map(rhsSubId => {
715
752
  if (isIndex(rhsSubId)) {
716
- return `[${subFamily}[(size_t)(${subBase} + ${offsetArgCode})]]`
753
+ let indexDecl
754
+ switch (ctx.outFormat) {
755
+ case 'c':
756
+ indexDecl = `(size_t)(${subBase} + ${offsetArgCode})`
757
+ break
758
+ case 'js':
759
+ indexDecl = `${subBase} + ${offsetArgCode}`
760
+ break
761
+ default:
762
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
763
+ }
764
+ return `[${subFamily}[${indexDecl}]]`
717
765
  } else {
718
766
  const subIndex = ctx.loopIndexVars.index(rhsSubId)
719
767
  return `[${rhsSubId}[${subIndex}]]`
@@ -725,11 +773,11 @@ function generateVectorElmMapCall(callExpr, ctx) {
725
773
  }
726
774
 
727
775
  /**
728
- * Generate C code for a `VECTOR SORT ORDER` function call.
776
+ * Generate C/JS code for a `VECTOR SORT ORDER` function call.
729
777
  *
730
778
  * @param {*} callExpr The function call expression from the parsed model.
731
779
  * @param {GenExprContext} ctx The context used when generating code for the expression.
732
- * @return {string} The generated C code.
780
+ * @return {string} The generated C/JS code.
733
781
  */
734
782
  function generateVectorSortOrderCall(callExpr, ctx) {
735
783
  // Process the vector argument
@@ -758,18 +806,27 @@ function generateVectorSortOrderCall(callExpr, ctx) {
758
806
  // Generate the code that is emitted before the entire block (before any loops are opened)
759
807
  const tmpVarId = newTmpVarName()
760
808
  const dimSize = sub(dimId).size
761
- ctx.emitPreInnerLoop(` double* ${tmpVarId} = _VECTOR_SORT_ORDER(${vecVarRefId}, ${dimSize}, ${dirArg});`)
809
+ switch (ctx.outFormat) {
810
+ case 'c':
811
+ ctx.emitPreInnerLoop(` double* ${tmpVarId} = _VECTOR_SORT_ORDER(${vecVarRefId}, ${dimSize}, ${dirArg});`)
812
+ break
813
+ case 'js':
814
+ ctx.emitPreInnerLoop(` let ${tmpVarId} = fns.VECTOR_SORT_ORDER(${vecVarRefId}, ${dimSize}, ${dirArg});`)
815
+ break
816
+ default:
817
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
818
+ }
762
819
 
763
820
  // Generate the RHS expression used in the inner loop
764
821
  return `${tmpVarId}[${dimId}[${subIndex}]]`
765
822
  }
766
823
 
767
824
  /**
768
- * Generate C code for an `ALLOCATE AVAILABLE` function call.
825
+ * Generate C/JS code for an `ALLOCATE AVAILABLE` function call.
769
826
  *
770
827
  * @param {*} callExpr The function call expression from the parsed model.
771
828
  * @param {GenExprContext} ctx The context used when generating code for the expression.
772
- * @return {string} The generated C code.
829
+ * @return {string} The generated C/JS code.
773
830
  */
774
831
  function generateAllocateAvailableCall(callExpr, ctx) {
775
832
  function validateArg(index, name) {
@@ -801,9 +858,20 @@ function generateAllocateAvailableCall(callExpr, ctx) {
801
858
  // Generate the code that is emitted before the entire block (before any loops are opened)
802
859
  const tmpVarId = newTmpVarName()
803
860
  const dimSize = sub(dimId).size
804
- ctx.emitPreInnerLoop(
805
- ` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${reqRefId}, (double*)${priorityRefId}, ${availRefId}, ${dimSize});`
806
- )
861
+ switch (ctx.outFormat) {
862
+ case 'c':
863
+ ctx.emitPreInnerLoop(
864
+ ` double* ${tmpVarId} = _ALLOCATE_AVAILABLE(${reqRefId}, (double*)${priorityRefId}, ${availRefId}, ${dimSize});`
865
+ )
866
+ break
867
+ case 'js':
868
+ ctx.emitPreInnerLoop(
869
+ ` let ${tmpVarId} = fns.ALLOCATE_AVAILABLE(${reqRefId}, ${priorityRefId}, ${availRefId}, ${dimSize});`
870
+ )
871
+ break
872
+ default:
873
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
874
+ }
807
875
 
808
876
  // Generate the RHS expression used in the inner loop
809
877
  return `${tmpVarId}[${dimId}[${subIndex}]]`
@@ -849,3 +917,92 @@ function visitVariableRefs(expr, onVarRef) {
849
917
  throw new Error(`Unhandled expression kind '${expr.kind}' in visitVariableRefs`)
850
918
  }
851
919
  }
920
+
921
+ /**
922
+ * Return a C or JS function reference for the given function ID and context.
923
+ *
924
+ * @param {string} fnId The function ID.
925
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
926
+ * @return {string} The generated C/JS code.
927
+ */
928
+ function fnRef(fnId, ctx) {
929
+ switch (ctx.outFormat) {
930
+ case 'c':
931
+ return fnId
932
+ case 'js':
933
+ return `fns.${fnId.slice(1)}`
934
+ default:
935
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
936
+ }
937
+ }
938
+
939
+ /**
940
+ * Return a C or JS variable declaration.
941
+ *
942
+ * @param {string} cVarType The variable type (only used for C code generation).
943
+ * @param {string} varName The variable name.
944
+ * @param {string} rhs The RHS for the declaration.
945
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
946
+ * @return {string} The generated C/JS code.
947
+ */
948
+ function varDecl(cVarType, varName, rhs, ctx) {
949
+ switch (ctx.outFormat) {
950
+ case 'c':
951
+ return `${cVarType} ${varName} = ${rhs};`
952
+ case 'js':
953
+ return `let ${varName} = ${rhs};`
954
+ default:
955
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Return the "max number" constant for C or JS.
961
+ *
962
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
963
+ * @return {string} The generated C/JS code.
964
+ */
965
+ function maxNumber(ctx) {
966
+ switch (ctx.outFormat) {
967
+ case 'c':
968
+ return 'DBL_MAX'
969
+ case 'js':
970
+ return 'Number.MAX_VALUE'
971
+ default:
972
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
973
+ }
974
+ }
975
+
976
+ /**
977
+ * Return the "max" function for C or JS.
978
+ *
979
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
980
+ * @return {string} The generated C/JS code.
981
+ */
982
+ function maxFunc(ctx) {
983
+ switch (ctx.outFormat) {
984
+ case 'c':
985
+ return 'fmax'
986
+ case 'js':
987
+ return 'Math.max'
988
+ default:
989
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
990
+ }
991
+ }
992
+
993
+ /**
994
+ * Return the "min" function for C or JS.
995
+ *
996
+ * @param {GenExprContext} ctx The context used when generating code for the expression.
997
+ * @return {string} The generated C/JS code.
998
+ */
999
+ function minFunc(ctx) {
1000
+ switch (ctx.outFormat) {
1001
+ case 'c':
1002
+ return 'fmin'
1003
+ case 'js':
1004
+ return 'Math.min'
1005
+ default:
1006
+ throw new Error(`Unhandled output format '${ctx.outFormat}'`)
1007
+ }
1008
+ }
@@ -15,11 +15,12 @@ import { handleExcelOrCsvFile } from './direct-data-helpers.js'
15
15
  * @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA` call (e.g.,
16
16
  * `?data`) to the tabular data contained in the loaded data file.
17
17
  * @param {string} modelDir The path to the directory containing the model (used for resolving data files).
18
- * @param {string} varLhs The C code for the LHS variable reference.
19
- * @return {string[]} An array of strings containing the generated C code for the variable,
18
+ * @param {string} varLhs The C/JS code for the LHS variable reference.
19
+ * @param {'c' | 'js'} outFormat The output format.
20
+ * @return {string[]} An array of strings containing the generated C/JS code for the variable,
20
21
  * one string per line of code.
21
22
  */
22
- export function generateLookupsFromDirectData(variable, mode, directData, modelDir, varLhs) {
23
+ export function generateLookupsFromDirectData(variable, mode, directData, modelDir, varLhs, outFormat) {
23
24
  if (mode === 'decl') {
24
25
  // Nothing to emit in decl mode
25
26
  return []
@@ -52,10 +53,10 @@ export function generateLookupsFromDirectData(variable, mode, directData, modelD
52
53
  }
53
54
  }
54
55
  }
55
- return [generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell, indexNum)]
56
+ return [generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell, indexNum, outFormat)]
56
57
  }
57
58
 
58
- function generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell, indexNum) {
59
+ function generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell, indexNum, outFormat) {
59
60
  // Read a row or column of data as (time, value) pairs from the worksheet.
60
61
  // The cell(c,r) function wraps data access by column and row.
61
62
  let lookupData = ''
@@ -101,5 +102,12 @@ function generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell,
101
102
  throw new Error(`Empty lookup data array for ${varLhs}`)
102
103
  }
103
104
 
104
- return ` ${varLhs} = __new_lookup(${lookupSize}, /*copy=*/true, (double[]){ ${lookupData} });`
105
+ switch (outFormat) {
106
+ case 'c':
107
+ return ` ${varLhs} = __new_lookup(${lookupSize}, /*copy=*/true, (double[]){ ${lookupData} });`
108
+ case 'js':
109
+ return ` ${varLhs} = fns.createLookup(${lookupSize}, [${lookupData}]);`
110
+ default:
111
+ throw new Error(`Unhandled output format '${outFormat}'`)
112
+ }
105
113
  }