@@ -18,6 +18,7 @@ import type { ModelicaDAE, ModelicaExpression } from "./dae.js";
1818import {
1919 ModelicaBinaryExpression ,
2020 ModelicaBooleanLiteral ,
21+ ModelicaFunctionCallEquation ,
2122 ModelicaFunctionCallExpression ,
2223 ModelicaIfElseExpression ,
2324 ModelicaInitialStateEquation ,
@@ -64,8 +65,13 @@ export function generateFmuCSources(dae: ModelicaDAE, fmuResult: FmuResult, opti
6465 // ── fmi3Functions.c ──
6566 const fmi3FunctionsC = generateFmi3FunctionsC ( id , dae ) ;
6667
68+ const externalLibs = new Set < string > ( ) ;
69+ for ( const fn of dae . functions ) {
70+ for ( const lib of fn . externalLibraries ) externalLibs . add ( lib ) ;
71+ }
72+
6773 // ── CMakeLists.txt ──
68- const cmakeLists = generateCMakeLists ( id ) ;
74+ const cmakeLists = generateCMakeLists ( id , Array . from ( externalLibs ) ) ;
6975
7076 return { modelH, modelC, fmi2FunctionsC, fmi3FunctionsC, cmakeLists } ;
7177}
@@ -123,11 +129,15 @@ function exprToC(expr: ModelicaExpression): string {
123129 // Message is a string literal — extract it or use a default
124130 const msgExpr = expr . args [ 1 ] ;
125131 const msg = msgExpr instanceof ModelicaStringLiteral ? msgExpr . value . replace ( / " / g, '\\"' ) : "Assertion failed" ;
126- return `((${ cond } ) ? 0.0 : (inst->callbacks. logger(inst, inst->instanceName, fmi2Error, "assert", "${ msg } "), 0.0))` ;
132+ return `((${ cond } ) ? 0.0 : (inst->logger ? inst->logger(inst->fmuInstance, "assert", "${ msg } ") : (void)0, inst->terminate ? inst->terminate(inst->fmuInstance) : (void)0 , 0.0))` ;
127133 }
128134 // terminate(message) — signal simulation termination
129135 if ( expr . functionName === "terminate" ) {
130- return "(inst->terminateRequested = 1, 0.0)" ;
136+ const msg =
137+ expr . args [ 0 ] instanceof ModelicaStringLiteral
138+ ? expr . args [ 0 ] . value . replace ( / " / g, '\\"' )
139+ : "Simulation terminated" ;
140+ return `(inst->logger ? inst->logger(inst->fmuInstance, "terminate", "${ msg } ") : (void)0, inst->terminate ? inst->terminate(inst->fmuInstance) : (void)0, 0.0)` ;
131141 }
132142 // spatialDistribution(in0, in1, x, positiveVelocity) — 1D advection
133143 if ( expr . functionName === "spatialDistribution" && expr . args . length >= 4 ) {
@@ -420,6 +430,9 @@ function generateModelH(
420430 ) ;
421431 }
422432
433+ lines . push ( ` void* fmuInstance; /* parent FMU struct pointer */` ) ;
434+ lines . push ( ` void (*logger)(void* fmuInstance, const char* category, const char* message);` ) ;
435+ lines . push ( ` void (*terminate)(void* fmuInstance);` ) ;
423436 lines . push ( `} ${ id } _Instance;` ) ;
424437 lines . push ( "" ) ;
425438 lines . push ( `void ${ id } _initialize(${ id } _Instance* inst);` ) ;
@@ -433,7 +446,14 @@ function generateModelH(
433446function generateAlgebraicLoopSolvers ( id : string , dae : ModelicaDAE , result : FmuResult ) : string {
434447 const lines : string [ ] = [ ] ;
435448 lines . push ( "/* Numerical solver for Algebraic Loops */" ) ;
436- lines . push ( "static void solve_linear_sys(int n, double* A, double* b, double* x) {" ) ;
449+ lines . push ( `#define LOG_ERROR(inst, msg) \\` ) ;
450+ lines . push ( ` do { \\` ) ;
451+ lines . push ( ` if ((inst)->logger) (inst)->logger((inst)->fmuInstance, "error", msg); \\` ) ;
452+ lines . push ( ` if ((inst)->terminate) (inst)->terminate((inst)->fmuInstance); \\` ) ;
453+ lines . push ( ` } while(0)` ) ;
454+ lines . push ( "" ) ;
455+ lines . push ( "static void solve_linear_sys(void* inst_ptr, int n, double* A, double* b, double* x) {" ) ;
456+ lines . push ( ` ${ id } _Instance* inst = (${ id } _Instance*)inst_ptr;` ) ;
437457 lines . push ( " for (int i = 0; i < n; i++) {" ) ;
438458 lines . push ( " int pivot = i;" ) ;
439459 lines . push ( " for (int j = i + 1; j < n; j++) {" ) ;
@@ -443,16 +463,20 @@ function generateAlgebraicLoopSolvers(id: string, dae: ModelicaDAE, result: FmuR
443463 lines . push ( " double tmp = A[i*n + j]; A[i*n + j] = A[pivot*n + j]; A[pivot*n + j] = tmp;" ) ;
444464 lines . push ( " }" ) ;
445465 lines . push ( " double tmp = b[i]; b[i] = b[pivot]; b[pivot] = tmp;" ) ;
466+ lines . push ( " if (fabs(A[i*n + i]) < 1e-14) {" ) ;
467+ lines . push ( ` LOG_ERROR(inst, "Singular algebraic loop matrix encountered");` ) ;
468+ lines . push ( " return;" ) ;
469+ lines . push ( " }" ) ;
446470 lines . push ( " for (int j = i + 1; j < n; j++) {" ) ;
447- lines . push ( " double factor = A[i*n + i] == 0.0 ? 0.0 : A[ j*n + i] / A[i*n + i];" ) ;
471+ lines . push ( " double factor = A[j*n + i] / A[i*n + i];" ) ;
448472 lines . push ( " for (int k = i; k < n; k++) A[j*n + k] -= factor * A[i*n + k];" ) ;
449473 lines . push ( " b[j] -= factor * b[i];" ) ;
450474 lines . push ( " }" ) ;
451475 lines . push ( " }" ) ;
452476 lines . push ( " for (int i = n - 1; i >= 0; i--) {" ) ;
453477 lines . push ( " double sum = 0.0;" ) ;
454478 lines . push ( " for (int j = i + 1; j < n; j++) sum += A[i*n + j] * x[j];" ) ;
455- lines . push ( " x[i] = A[i*n + i] == 0.0 ? 0.0 : (b[i] - sum) / A[i*n + i];" ) ;
479+ lines . push ( " x[i] = (b[i] - sum) / A[i*n + i];" ) ;
456480 lines . push ( " }" ) ;
457481 lines . push ( "}" ) ;
458482 lines . push ( "" ) ;
@@ -539,7 +563,7 @@ function generateAlgebraicLoopSolvers(id: string, dae: ModelicaDAE, result: FmuR
539563 lines . push ( ` }` ) ;
540564
541565 lines . push ( ` for (int i = 0; i < ${ N } ; i++) F[i] = -F[i];` ) ;
542- lines . push ( ` solve_linear_sys(${ N } , J, F, dx);` ) ;
566+ lines . push ( ` solve_linear_sys(inst, ${ N } , J, F, dx);` ) ;
543567 lines . push ( ` for (int j = 0; j < ${ N } ; j++) {` ) ;
544568 lines . push ( ` int vr = -1;` ) ;
545569 for ( let j = 0 ; j < N ; j ++ ) {
@@ -563,6 +587,19 @@ function generateModelC(id: string, dae: ModelicaDAE, result: FmuResult): string
563587 lines . push ( "/* Auto-generated by ModelScript — do not edit */" ) ;
564588 lines . push ( `#include "${ id } _model.h"` ) ;
565589 lines . push ( "#include <stdio.h>" ) ;
590+
591+ const externalIncludes = new Set < string > ( ) ;
592+ for ( const fn of dae . functions ) {
593+ for ( const inc of fn . externalIncludes ) externalIncludes . add ( inc ) ;
594+ }
595+ for ( const inc of externalIncludes ) {
596+ if ( inc . trim ( ) . startsWith ( "#" ) ) {
597+ lines . push ( inc ) ;
598+ } else {
599+ // Sometimes just a header file name is given
600+ lines . push ( inc . includes ( ";" ) || inc . includes ( "int " ) || inc . includes ( "void " ) ? inc : `#include "${ inc } "` ) ;
601+ }
602+ }
566603 lines . push ( "" ) ;
567604
568605 // Count delay() calls to determine if delay helpers are needed
@@ -876,6 +913,21 @@ function generateFmi2FunctionsC(
876913 lines . push ( "} FMUInstance;" ) ;
877914 lines . push ( "" ) ;
878915
916+ // ── Logger & Terminate Impl ──
917+ lines . push ( "static void fmi2_logger_impl(void* fmuInstance, const char* category, const char* message) {" ) ;
918+ lines . push ( " FMUInstance* inst = (FMUInstance*)fmuInstance;" ) ;
919+ lines . push ( " if (inst->callbacks.logger) {" ) ;
920+ lines . push (
921+ " inst->callbacks.logger(inst->callbacks.componentEnvironment, inst->instanceName, fmi2Error, category, message);" ,
922+ ) ;
923+ lines . push ( " }" ) ;
924+ lines . push ( "}" ) ;
925+ lines . push ( "static void fmi2_terminate_impl(void* fmuInstance) {" ) ;
926+ lines . push ( " FMUInstance* inst = (FMUInstance*)fmuInstance;" ) ;
927+ lines . push ( " inst->terminateRequested = 1;" ) ;
928+ lines . push ( "}" ) ;
929+ lines . push ( "" ) ;
930+
879931 // ── fmi2Instantiate ──
880932 lines . push ( "fmi2Component fmi2Instantiate(fmi2String instanceName, fmi2Type fmuType," ) ;
881933 lines . push ( " fmi2String fmuGUID, fmi2String fmuResourceLocation," ) ;
@@ -887,6 +939,9 @@ function generateFmi2FunctionsC(
887939 lines . push ( " inst->callbacks = *functions;" ) ;
888940 lines . push ( " inst->loggingOn = loggingOn;" ) ;
889941 lines . push ( ` inst->stepSize = ${ result . modelStructure . derivatives . length > 0 ? "0.001" : "0.001" } ;` ) ;
942+ lines . push ( " inst->model.fmuInstance = inst;" ) ;
943+ lines . push ( " inst->model.logger = fmi2_logger_impl;" ) ;
944+ lines . push ( " inst->model.terminate = fmi2_terminate_impl;" ) ;
890945 lines . push ( ` ${ id } _initialize(&inst->model);` ) ;
891946 lines . push ( " return (fmi2Component)inst;" ) ;
892947 lines . push ( "}" ) ;
@@ -1394,6 +1449,16 @@ function generateFmi2FunctionsC(
13941449 lines . push ( "/* --- Stubs --- */" ) ;
13951450 lines . push ( "fmi2Status fmi2Reset(fmi2Component c) {" ) ;
13961451 lines . push ( " FMUInstance* inst = (FMUInstance*)c;" ) ;
1452+ for ( let ei = 0 ; ei < dae . externalObjects . length ; ei ++ ) {
1453+ const eo = dae . externalObjects [ ei ] ;
1454+ if ( ! eo ) continue ;
1455+ const ctorName = sanitizeIdentifier ( eo . constructorName ) ;
1456+ const dtorName = sanitizeIdentifier ( eo . destructorName ) ;
1457+ lines . push ( ` if (inst->model.extObj_${ ei } ) {` ) ;
1458+ lines . push ( ` ${ dtorName } (inst->model.extObj_${ ei } );` ) ;
1459+ lines . push ( ` }` ) ;
1460+ lines . push ( ` inst->model.extObj_${ ei } = ${ ctorName } ();` ) ;
1461+ }
13971462 lines . push ( ` ${ id } _initialize(&inst->model);` ) ;
13981463 lines . push ( " return fmi2OK;" ) ;
13991464 lines . push ( "}" ) ;
@@ -1522,6 +1587,20 @@ function generateFmi2FunctionsC(
15221587 ) ;
15231588 }
15241589 }
1590+ } else if ( bodyEq instanceof ModelicaFunctionCallEquation ) {
1591+ if ( bodyEq . call . functionName === "reinit" && bodyEq . call . args . length === 2 ) {
1592+ const stateRef = bodyEq . call . args [ 0 ] ;
1593+ const newValue = bodyEq . call . args [ 1 ] ;
1594+ if ( stateRef instanceof ModelicaNameExpression && newValue ) {
1595+ const sv = result . scalarVariables . find ( ( v ) => v . name === stateRef . name ) ;
1596+ if ( sv ) {
1597+ lines . push (
1598+ ` inst->model.vars[${ sv . valueReference } ] = ${ exprToC ( newValue ) } ; /* reinit(${ stateRef . name } ) */` ,
1599+ ) ;
1600+ lines . push ( ` info->valuesOfContinuousStatesChanged = fmi2True;` ) ;
1601+ }
1602+ }
1603+ }
15251604 }
15261605 }
15271606 lines . push ( " }" ) ;
@@ -1544,6 +1623,20 @@ function generateFmi2FunctionsC(
15441623 ) ;
15451624 }
15461625 }
1626+ } else if ( bodyEq instanceof ModelicaFunctionCallEquation ) {
1627+ if ( bodyEq . call . functionName === "reinit" && bodyEq . call . args . length === 2 ) {
1628+ const stateRef = bodyEq . call . args [ 0 ] ;
1629+ const newValue = bodyEq . call . args [ 1 ] ;
1630+ if ( stateRef instanceof ModelicaNameExpression && newValue ) {
1631+ const sv = result . scalarVariables . find ( ( v ) => v . name === stateRef . name ) ;
1632+ if ( sv ) {
1633+ lines . push (
1634+ ` inst->model.vars[${ sv . valueReference } ] = ${ exprToC ( newValue ) } ; /* reinit(${ stateRef . name } ) */` ,
1635+ ) ;
1636+ lines . push ( ` info->valuesOfContinuousStatesChanged = fmi2True;` ) ;
1637+ }
1638+ }
1639+ }
15471640 }
15481641 }
15491642 lines . push ( " }" ) ;
@@ -1833,8 +1926,7 @@ function generateFmi2FunctionsC(
18331926
18341927// ── CMakeLists.txt generator ──
18351928
1836- function generateCMakeLists ( id : string , externalSources : string [ ] = [ ] ) : string {
1837- const extSourceLines = externalSources . map ( ( s ) => ` ${ s } ` ) . join ( "\n" ) ;
1929+ function generateCMakeLists ( id : string , externalLibraries : string [ ] = [ ] ) : string {
18381930 return `# Auto-generated by ModelScript — CMake build for FMU shared library
18391931cmake_minimum_required(VERSION 3.10)
18401932project(${ id } C)
@@ -1860,11 +1952,13 @@ endif()
18601952add_library(${ id } SHARED
18611953 ${ id } _model.c
18621954 fmi2Functions.c
1863- fmi3Functions.c${ extSourceLines ? "\n" + extSourceLines : "" }
1955+ fmi3Functions.c
18641956)
18651957
18661958target_include_directories(${ id } PRIVATE \${CMAKE_CURRENT_SOURCE_DIR})
18671959
1960+ ${ externalLibraries . length > 0 ? `target_link_libraries(${ id } PRIVATE ${ externalLibraries . join ( " " ) . replace ( / \\ / g, "/" ) } )` : "" }
1961+
18681962# Export FMI symbols, hide everything else
18691963set_target_properties(${ id } PROPERTIES
18701964 PREFIX ""
0 commit comments