Skip to content

Commit 6d377c5

Browse files
committed
feat(core): synchronous clocks, state machine codegen, delay buffers, spatialDistribution, operator record dispatch, ExternalObject lifecycle
1 parent 43bedf3 commit 6d377c5

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

packages/core/src/compiler/modelica/dae.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export class ModelicaDAE {
3131
functions: ModelicaDAE[] = [];
3232
/** External function declaration text (e.g. `external "C" ...`). */
3333
externalDecl: string | null = null;
34+
/**
35+
* Descriptors for variables whose type extends `ExternalObject`.
36+
* Track constructor/destructor names for lifecycle management.
37+
*/
38+
externalObjects: ModelicaExternalObjectDescriptor[] = [];
3439
/** Diagnostics emitted during flattening (e.g. type errors, invalid iterators). */
3540
diagnostics: ModelicaDiagnostic[] = [];
3641
/** Experiment annotation data (StartTime, StopTime, Tolerance, etc.). */
@@ -3362,6 +3367,45 @@ export class ModelicaClockPartition {
33623367
}
33633368
}
33643369

3370+
/**
3371+
* Describes a variable whose type extends `ExternalObject`.
3372+
* Tracks the constructor and destructor function names for lifecycle management
3373+
* during FMU initialization and termination.
3374+
*/
3375+
export class ModelicaExternalObjectDescriptor {
3376+
/** The variable name in the flattened DAE. */
3377+
variableName: string;
3378+
/** The fully-qualified type name (e.g., `MyLib.MyExternalObj`). */
3379+
typeName: string;
3380+
/** Constructor function name (e.g., `MyExternalObj.constructor`). */
3381+
constructorName: string;
3382+
/** Destructor function name (e.g., `MyExternalObj.destructor`). */
3383+
destructorName: string;
3384+
3385+
constructor(variableName: string, typeName: string, constructorName?: string, destructorName?: string) {
3386+
this.variableName = variableName;
3387+
this.typeName = typeName;
3388+
this.constructorName = constructorName ?? `${typeName}.constructor`;
3389+
this.destructorName = destructorName ?? `${typeName}.destructor`;
3390+
}
3391+
3392+
get hash(): string {
3393+
const hash = createHash("sha256");
3394+
hash.update("externalObject_" + this.variableName + "_" + this.typeName);
3395+
return hash.digest("hex");
3396+
}
3397+
3398+
get toJSON(): JSONValue {
3399+
return {
3400+
"@type": "ExternalObjectDescriptor",
3401+
variableName: this.variableName,
3402+
typeName: this.typeName,
3403+
constructorName: this.constructorName,
3404+
destructorName: this.destructorName,
3405+
};
3406+
}
3407+
}
3408+
33653409
export class ModelicaState {
33663410
name: string;
33673411
variables: ModelicaVariable[] = [];

packages/core/src/compiler/modelica/flattener.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2841,6 +2841,33 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
28412841
}
28422842
}
28432843

2844+
// Operator record dispatch for String(x): when the argument is an operator record,
2845+
// rewrite String(x) as RecordType.'String'(x) using the operator function.
2846+
if (functionName === "String" && flatArgs.length >= 1) {
2847+
const arg0 = flatArgs[0];
2848+
if (arg0) {
2849+
const stringOp = this.#resolveOperatorRecordFunction(arg0, "'String'", ctx);
2850+
if (stringOp) {
2851+
const { qualifiedName, resolvedClass } = stringOp;
2852+
this.#collectFunctionDefinition(qualifiedName, ctx, resolvedClass);
2853+
return new ModelicaFunctionCallExpression(qualifiedName, flatArgs);
2854+
}
2855+
}
2856+
}
2857+
2858+
// Operator record dispatch for '0' (zero literal): when zeros() is called with
2859+
// an operator record type argument, rewrite to RecordType.'0'() zero constructor.
2860+
if (functionName === "zeros" && flatArgs.length >= 1) {
2861+
const arg0 = flatArgs[0];
2862+
if (arg0) {
2863+
const zeroOp = this.#resolveOperatorRecordFunction(arg0, "'0'", ctx);
2864+
if (zeroOp) {
2865+
const { qualifiedName, resolvedClass } = zeroOp;
2866+
this.#collectFunctionDefinition(qualifiedName, ctx, resolvedClass);
2867+
return new ModelicaFunctionCallExpression(qualifiedName, []);
2868+
}
2869+
}
2870+
}
28442871
// Pre-expansion size() resolution: resolve size(var, dim) directly from
28452872
// class instance BEFORE arg expansion may lose inner dimension info.
28462873
// E.g., size(b, 2) where b is Real[2, 0] — expansion loses the 0 dimension.
@@ -6289,6 +6316,13 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
62896316
const negatedFirst = new ModelicaUnaryExpression(ModelicaUnaryOperator.UNARY_MINUS, operand.operand1);
62906317
return new ModelicaBinaryExpression(operand.operator, negatedFirst, operand.operand2);
62916318
}
6319+
// Operator record dispatch: unary minus → RecordType.'-'.negate(x)
6320+
const operatorInfo = this.#resolveOperatorRecordFunction(operand, "'-'", ctx);
6321+
if (operatorInfo) {
6322+
const { qualifiedName, resolvedClass } = operatorInfo;
6323+
this.#collectFunctionDefinition(qualifiedName, ctx, resolvedClass);
6324+
return new ModelicaFunctionCallExpression(qualifiedName, [operand]);
6325+
}
62926326
}
62936327
if (operator === ModelicaUnaryOperator.UNARY_PLUS) {
62946328
if (operand instanceof ModelicaRealLiteral || operand instanceof ModelicaIntegerLiteral) return operand;

packages/core/src/compiler/modelica/fmu-codegen.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,12 @@ function generateFmi2FunctionsC(
536536
` int activeState_${si}; /* 0-indexed into states of SM ${si}: ${dae.stateMachines[si]?.name ?? ""} */`,
537537
);
538538
}
539+
// ExternalObject handle fields
540+
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
541+
const eo = dae.externalObjects[ei];
542+
if (!eo) continue;
543+
lines.push(` void* extObj_${ei}; /* ${eo.typeName}: ${eo.variableName} */`);
544+
}
539545
lines.push("} FMUInstance;");
540546
lines.push("");
541547

@@ -555,6 +561,17 @@ function generateFmi2FunctionsC(
555561
lines.push("}");
556562
lines.push("");
557563

564+
// Emit ExternalObject constructor calls right after fmi2Instantiate
565+
if (dae.externalObjects.length > 0) {
566+
lines.push("/* --- ExternalObject constructor stubs --- */");
567+
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
568+
const eo = dae.externalObjects[ei];
569+
if (!eo) continue;
570+
lines.push(`/* TODO: inst->extObj_${ei} = ${sanitizeIdentifier(eo.constructorName)}(...); */`);
571+
}
572+
lines.push("");
573+
}
574+
558575
// ── fmi2SetupExperiment ──
559576
lines.push("fmi2Status fmi2SetupExperiment(fmi2Component c, fmi2Boolean toleranceDefined,");
560577
lines.push(" fmi2Real tolerance, fmi2Real startTime, fmi2Boolean stopTimeDefined, fmi2Real stopTime) {");
@@ -831,6 +848,15 @@ function generateFmi2FunctionsC(
831848
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
832849
lines.push(" if (inst->model.stringVars[i]) free(inst->model.stringVars[i]);");
833850
lines.push(" }");
851+
// ExternalObject destructor stubs
852+
if (dae.externalObjects.length > 0) {
853+
lines.push(" /* ExternalObject destructors */");
854+
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
855+
const eo = dae.externalObjects[ei];
856+
if (!eo) continue;
857+
lines.push(` /* TODO: ${sanitizeIdentifier(eo.destructorName)}(inst->extObj_${ei}); */`);
858+
}
859+
}
834860
lines.push(" free(inst);");
835861
lines.push("}");
836862
lines.push("");

0 commit comments

Comments
 (0)