@@ -20,6 +20,7 @@ import {
2020 ModelicaBooleanLiteral ,
2121 ModelicaFunctionCallExpression ,
2222 ModelicaIfElseExpression ,
23+ ModelicaInitialStateEquation ,
2324 ModelicaIntegerLiteral ,
2425 ModelicaNameExpression ,
2526 ModelicaRealLiteral ,
@@ -72,6 +73,9 @@ export function generateFmuCSources(dae: ModelicaDAE, fmuResult: FmuResult, opti
7273// ── Expression → C transpiler ──
7374
7475/** Convert a DAE expression to a C expression string. */
76+ /** Counter for deterministic delay buffer indexing across exprToC calls. */
77+ let delayBufferCounter = 0 ;
78+
7579function exprToC ( expr : ModelicaExpression ) : string {
7680 if ( expr instanceof ModelicaRealLiteral ) {
7781 return formatCDouble ( expr . value ) ;
@@ -100,6 +104,14 @@ function exprToC(expr: ModelicaExpression): string {
100104 return `(${ lhs } ${ op } ${ rhs } )` ;
101105 }
102106 if ( expr instanceof ModelicaFunctionCallExpression ) {
107+ // Special handling for delay() — generate ring-buffer interpolation lookup
108+ if ( expr . functionName === "delay" && expr . args . length >= 2 ) {
109+ const delayExpr = exprToC ( expr . args [ 0 ] as ModelicaExpression ) ;
110+ const delayTime = exprToC ( expr . args [ 1 ] as ModelicaExpression ) ;
111+ // Use a deterministic buffer index based on expression hash
112+ const bufIdx = delayBufferCounter ++ ;
113+ return `delay_lookup(&m->delayBuf[${ bufIdx } ], m->time - (${ delayTime } ), ${ delayExpr } )` ;
114+ }
103115 const args = expr . args . map ( ( a : ModelicaExpression ) => exprToC ( a ) ) . join ( ", " ) ;
104116 const fname = mapFunctionName ( expr . functionName ) ;
105117 return `${ fname } (${ args } )` ;
@@ -508,12 +520,80 @@ function generateAlgebraicLoopSolvers(id: string, dae: ModelicaDAE, result: FmuR
508520}
509521
510522function generateModelC ( id : string , dae : ModelicaDAE , result : FmuResult ) : string {
523+ // Reset delay buffer counter for this codegen invocation
524+ delayBufferCounter = 0 ;
525+
511526 const lines : string [ ] = [ ] ;
512527 lines . push ( "/* Auto-generated by ModelScript — do not edit */" ) ;
513528 lines . push ( `#include "${ id } _model.h"` ) ;
514529 lines . push ( "#include <stdio.h>" ) ;
515530 lines . push ( "" ) ;
516531
532+ // Count delay() calls to determine if delay helpers are needed
533+ let hasDelays = false ;
534+ const checkDelays = ( expr : ModelicaExpression ) : void => {
535+ if ( expr instanceof ModelicaFunctionCallExpression && expr . functionName === "delay" ) {
536+ hasDelays = true ;
537+ return ;
538+ }
539+ if ( expr instanceof ModelicaFunctionCallExpression ) {
540+ for ( const arg of expr . args ) checkDelays ( arg ) ;
541+ }
542+ if ( "expression1" in expr && expr . expression1 ) checkDelays ( expr . expression1 as ModelicaExpression ) ;
543+ if ( "expression2" in expr && expr . expression2 ) checkDelays ( expr . expression2 as ModelicaExpression ) ;
544+ } ;
545+ for ( const eq of dae . equations ) {
546+ if ( eq instanceof ModelicaSimpleEquation ) {
547+ checkDelays ( eq . expression1 ) ;
548+ checkDelays ( eq . expression2 ) ;
549+ }
550+ }
551+
552+ if ( hasDelays ) {
553+ lines . push ( "/* --- Delay ring-buffer helpers --- */" ) ;
554+ lines . push ( "#define DELAY_BUF_SIZE 1024" ) ;
555+ lines . push ( "" ) ;
556+ lines . push ( "typedef struct {" ) ;
557+ lines . push ( " double times[DELAY_BUF_SIZE];" ) ;
558+ lines . push ( " double values[DELAY_BUF_SIZE];" ) ;
559+ lines . push ( " int head;" ) ;
560+ lines . push ( " int count;" ) ;
561+ lines . push ( "} DelayBuffer;" ) ;
562+ lines . push ( "" ) ;
563+ lines . push ( "static void delay_record(DelayBuffer* buf, double t, double value) {" ) ;
564+ lines . push ( " buf->times[buf->head] = t;" ) ;
565+ lines . push ( " buf->values[buf->head] = value;" ) ;
566+ lines . push ( " buf->head = (buf->head + 1) % DELAY_BUF_SIZE;" ) ;
567+ lines . push ( " if (buf->count < DELAY_BUF_SIZE) buf->count++;" ) ;
568+ lines . push ( "}" ) ;
569+ lines . push ( "" ) ;
570+ lines . push ( "static double delay_lookup(DelayBuffer* buf, double tLookup, double currentValue) {" ) ;
571+ lines . push ( " if (buf->count == 0) return currentValue;" ) ;
572+ lines . push ( " /* Find the two nearest entries bracketing tLookup and linearly interpolate */" ) ;
573+ lines . push ( " int start = (buf->head - buf->count + DELAY_BUF_SIZE) % DELAY_BUF_SIZE;" ) ;
574+ lines . push ( " int n = buf->count;" ) ;
575+ lines . push ( " /* If tLookup is before the earliest recorded time, return earliest value */" ) ;
576+ lines . push ( " double t0 = buf->times[start];" ) ;
577+ lines . push ( " if (tLookup <= t0) return buf->values[start];" ) ;
578+ lines . push ( " /* Linear search for bracketing interval */" ) ;
579+ lines . push ( " for (int k = 0; k < n - 1; k++) {" ) ;
580+ lines . push ( " int i0 = (start + k) % DELAY_BUF_SIZE;" ) ;
581+ lines . push ( " int i1 = (start + k + 1) % DELAY_BUF_SIZE;" ) ;
582+ lines . push ( " double ta = buf->times[i0];" ) ;
583+ lines . push ( " double tb = buf->times[i1];" ) ;
584+ lines . push ( " if (tLookup >= ta && tLookup <= tb) {" ) ;
585+ lines . push ( " if (tb == ta) return buf->values[i0];" ) ;
586+ lines . push ( " double alpha = (tLookup - ta) / (tb - ta);" ) ;
587+ lines . push ( " return buf->values[i0] * (1.0 - alpha) + buf->values[i1] * alpha;" ) ;
588+ lines . push ( " }" ) ;
589+ lines . push ( " }" ) ;
590+ lines . push ( " /* tLookup is after all recorded times — return latest */" ) ;
591+ lines . push ( " int last = (buf->head - 1 + DELAY_BUF_SIZE) % DELAY_BUF_SIZE;" ) ;
592+ lines . push ( " return buf->values[last];" ) ;
593+ lines . push ( "}" ) ;
594+ lines . push ( "" ) ;
595+ }
596+
517597 // Build value-reference → C accessor mappings
518598 const vrMap = new Map < string , number > ( ) ;
519599 for ( const sv of result . scalarVariables ) {
@@ -595,6 +675,36 @@ function generateModelC(id: string, dae: ModelicaDAE, result: FmuResult): string
595675 derIdx ++ ;
596676 }
597677 }
678+ if ( hasDelays ) {
679+ // Record current values of delay() expressions into ring buffers
680+ lines . push ( "" ) ;
681+ lines . push ( " /* Record delay buffer entries */" ) ;
682+ let delayIdx = 0 ;
683+ const collectDelayExprs = ( expr : ModelicaExpression , records : string [ ] ) : void => {
684+ if ( expr instanceof ModelicaFunctionCallExpression && expr . functionName === "delay" && expr . args . length >= 2 ) {
685+ const delayExprC = exprToC ( expr . args [ 0 ] as ModelicaExpression ) ;
686+ records . push ( ` delay_record(&inst->delayBuf[${ delayIdx } ], inst->time, ${ delayExprC } );` ) ;
687+ delayIdx ++ ;
688+ return ;
689+ }
690+ if ( expr instanceof ModelicaFunctionCallExpression ) {
691+ for ( const arg of expr . args ) collectDelayExprs ( arg , records ) ;
692+ }
693+ if ( "expression1" in expr && expr . expression1 ) collectDelayExprs ( expr . expression1 as ModelicaExpression , records ) ;
694+ if ( "expression2" in expr && expr . expression2 ) collectDelayExprs ( expr . expression2 as ModelicaExpression , records ) ;
695+ } ;
696+ const recordLines : string [ ] = [ ] ;
697+ for ( const eq of dae . equations ) {
698+ if ( eq instanceof ModelicaSimpleEquation ) {
699+ collectDelayExprs ( eq . expression1 , recordLines ) ;
700+ collectDelayExprs ( eq . expression2 , recordLines ) ;
701+ }
702+ }
703+ for ( const rl of recordLines ) {
704+ lines . push ( rl ) ;
705+ }
706+ }
707+
598708 lines . push ( "}" ) ;
599709 lines . push ( "" ) ;
600710
@@ -924,21 +1034,57 @@ function generateFmi2FunctionsC(
9241034 ) ;
9251035 transitions . sort ( ( a , b ) => a . priority - b . priority ) ;
9261036
1037+ // Find initial state
1038+ const initialStateEq = sm . equations . find (
1039+ ( eq ) : eq is ModelicaInitialStateEquation => eq instanceof ModelicaInitialStateEquation ,
1040+ ) ;
1041+ const initialIdx = initialStateEq ? stateNames . indexOf ( initialStateEq . stateName ) : 0 ;
1042+
9271043 if ( transitions . length > 0 ) {
928- lines . push ( ` /* SM ${ si } : ${ sm . name } */` ) ;
929- let first = true ;
1044+ lines . push ( ` /* SM ${ si } : ${ sm . name } (initial state: ${ stateNames [ initialIdx ] ?? "?" } ) */` ) ;
1045+ lines . push ( ` {` ) ;
1046+ lines . push ( ` int prevState_${ si } = inst->activeState_${ si } ;` ) ;
1047+
9301048 for ( const tr of transitions ) {
9311049 const fromIdx = stateNames . indexOf ( tr . fromState ) ;
9321050 const toIdx = stateNames . indexOf ( tr . toState ) ;
9331051 if ( fromIdx < 0 || toIdx < 0 ) continue ;
934- const prefix = first ? "if" : "else if" ;
935- first = false ;
936- lines . push (
937- ` ${ prefix } (inst->activeState_${ si } == ${ fromIdx } ) { /* ${ tr . fromState } -> ${ tr . toState } , priority ${ tr . priority } */` ,
938- ) ;
939- lines . push ( ` /* TODO: evaluate condition and set inst->activeState_${ si } = ${ toIdx } ; */` ) ;
940- lines . push ( " }" ) ;
1052+ const condC = exprToC ( tr . condition ) ;
1053+ if ( tr . immediate ) {
1054+ // Immediate transition: fires in the same step the condition becomes true
1055+ lines . push (
1056+ ` if (inst->activeState_${ si } == ${ fromIdx } && (${ condC } )) { /* ${ tr . fromState } -> ${ tr . toState } */` ,
1057+ ) ;
1058+ } else {
1059+ // Delayed transition: fires at the next step after the condition becomes true
1060+ lines . push (
1061+ ` if (prevState_${ si } == ${ fromIdx } && (${ condC } )) { /* ${ tr . fromState } -> ${ tr . toState } (delayed) */` ,
1062+ ) ;
1063+ }
1064+ lines . push ( ` inst->activeState_${ si } = ${ toIdx } ;` ) ;
1065+
1066+ // Reset semantics: re-initialize target state's variables to start values
1067+ if ( tr . reset ) {
1068+ const targetState = sm . states [ toIdx ] ;
1069+ if ( targetState ) {
1070+ for ( const v of targetState . variables ) {
1071+ const sv = result . scalarVariables . find ( ( s ) => s . name === v . name ) ;
1072+ if ( sv ) {
1073+ const startAttr = v . attributes . get ( "start" ) ;
1074+ const startExpr = startAttr ?? v . expression ;
1075+ if ( startExpr ) {
1076+ lines . push (
1077+ ` inst->model.vars[${ sv . valueReference } ] = ${ exprToC ( startExpr ) } ; /* reset ${ v . name } */` ,
1078+ ) ;
1079+ }
1080+ }
1081+ }
1082+ }
1083+ }
1084+
1085+ lines . push ( " }" ) ;
9411086 }
1087+ lines . push ( " }" ) ;
9421088 }
9431089 }
9441090 lines . push ( "" ) ;
0 commit comments