feat(core): implement external object lifecycles, wire assertion and … · modelscript/modelscript@70f6407 · GitHub
Skip to content

Commit 70f6407

Browse files
committed
feat(core): implement external object lifecycles, wire assertion and logging callbacks, map enumeration xml exports, add child_process cmake compilation route
1 parent 05dead9 commit 70f6407

6 files changed

Lines changed: 295 additions & 16 deletions

File tree

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

Lines changed: 4 additions & 0 deletions

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4886,6 +4886,38 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
48864886
}
48874887
declText += ";";
48884888
fnDae.externalDecl = declText;
4889+
4890+
if (ext.annotationClause?.classModification?.modificationArguments) {
4891+
for (const arg of ext.annotationClause.classModification.modificationArguments) {
4892+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4893+
const modArg = arg as any;
4894+
if (modArg.name?.text && modArg.modification?.expression) {
4895+
const nameText = modArg.name.text;
4896+
if (nameText === "Include" || nameText === "Library") {
4897+
const exprCsn = modArg.modification.expression.concreteSyntaxNode;
4898+
if (exprCsn) {
4899+
// Recursively extract all STRING tokens from the AST subtree
4900+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4901+
const extractStrings = (node: any): string[] => {
4902+
const result: string[] = [];
4903+
if (node.type === "STRING" && node.text) {
4904+
const text = node.text.substring(1, node.text.length - 1);
4905+
result.push(text.replace(/""/g, '"').replace(/\\"/g, '"'));
4906+
}
4907+
for (let i = 0; i < node.childCount; i++) {
4908+
const child = node.child(i);
4909+
if (child) result.push(...extractStrings(child));
4910+
}
4911+
return result;
4912+
};
4913+
const values = extractStrings(exprCsn);
4914+
if (nameText === "Include") fnDae.externalIncludes.push(...values);
4915+
else fnDae.externalLibraries.push(...values);
4916+
}
4917+
}
4918+
}
4919+
}
4920+
}
48894921
}
48904922
}
48914923
// fnDae was pushed early to prevent recursion during body flattening.

‎packages/core/src/compiler/modelica/fmi.ts‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,9 +597,11 @@ function generateModelDescriptionXml(
597597
for (const [typeName, literals] of opts.enumTypes) {
598598
lines.push(` <SimpleType name="${escapeXml(typeName)}">`);
599599
lines.push(" <Enumeration>");
600-
for (const lit of literals) {
600+
for (let i = 0; i < literals.length; i++) {
601+
const lit = literals[i];
602+
if (!lit) continue;
601603
const descAttr = lit.description ? ` description="${escapeXml(lit.description)}"` : "";
602-
lines.push(` <Item name="${escapeXml(lit.name)}"${descAttr} />`);
604+
lines.push(` <Item name="${escapeXml(lit.name)}" value="${i + 1}"${descAttr} />`);
603605
}
604606
lines.push(" </Enumeration>");
605607
lines.push(" </SimpleType>");
@@ -781,6 +783,22 @@ export function generateFmi3ModelDescriptionXml(
781783
lines.push(` stopTime="${opts.stopTime ?? 1}"`);
782784
lines.push(` stepSize="${opts.stepSize ?? 0.001}" />`);
783785

786+
if (opts.enumTypes.size > 0) {
787+
lines.push("");
788+
lines.push(" <TypeDefinitions>");
789+
for (const [typeName, literals] of opts.enumTypes) {
790+
lines.push(` <EnumerationType name="${escapeXml(typeName)}">`);
791+
for (let i = 0; i < literals.length; i++) {
792+
const lit = literals[i];
793+
if (!lit) continue;
794+
const descAttr = lit.description ? ` description="${escapeXml(lit.description)}"` : "";
795+
lines.push(` <Item name="${escapeXml(lit.name)}" value="${i + 1}"${descAttr} />`);
796+
}
797+
lines.push(" </EnumerationType>");
798+
}
799+
lines.push(" </TypeDefinitions>");
800+
}
801+
784802
lines.push("");
785803
lines.push(" <ModelVariables>");
786804
const groupedVars = groupFmi3Variables(variables);

‎packages/core/src/compiler/modelica/fmu-archive.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,47 @@ fmi2Status fmi2GetRealOutputDerivatives(fmi2Component, const fmi2ValueReference[
371371
372372
#endif
373373
`;
374+
375+
/**
376+
* Compiles the generated C sources into a shared library (.dll, .so, .dylib) via CMake.
377+
* NOTE: This requires Node.js (fs, path, os, child_process), CMake, and a C compiler available on the system.
378+
*/
379+
export async function compileFmuBinary(
380+
id: string,
381+
sources: { modelC: string; modelH: string; fmi2FunctionsC: string; fmi3FunctionsC: string; cmakeLists: string },
382+
): Promise<Uint8Array> {
383+
const [fs, path, os, { execSync }] = await Promise.all([
384+
import("fs"),
385+
import("path"),
386+
import("os"),
387+
import("child_process"),
388+
]);
389+
390+
const tmpPrefix = path.join(os.tmpdir(), `modelscript-fmu-${id}-`);
391+
const tmpDir = fs.mkdtempSync(tmpPrefix);
392+
393+
try {
394+
fs.writeFileSync(path.join(tmpDir, `${id}_model.c`), sources.modelC);
395+
fs.writeFileSync(path.join(tmpDir, `${id}_model.h`), sources.modelH);
396+
fs.writeFileSync(path.join(tmpDir, "fmi2Functions.c"), sources.fmi2FunctionsC);
397+
fs.writeFileSync(path.join(tmpDir, "fmi3Functions.c"), sources.fmi3FunctionsC);
398+
fs.writeFileSync(path.join(tmpDir, "fmi2Functions.h"), FMI2_FUNCTIONS_H);
399+
fs.writeFileSync(path.join(tmpDir, "fmi2TypesPlatform.h"), FMI2_TYPES_PLATFORM_H);
400+
fs.writeFileSync(path.join(tmpDir, "fmi2FunctionTypes.h"), FMI2_FUNCTION_TYPES_H);
401+
fs.writeFileSync(path.join(tmpDir, "CMakeLists.txt"), sources.cmakeLists);
402+
403+
execSync(`cmake -B build -S . -DCMAKE_BUILD_TYPE=Release`, { cwd: tmpDir, stdio: "pipe" });
404+
execSync(`cmake --build build --config Release`, { cwd: tmpDir, stdio: "pipe" });
405+
406+
const buildDir = path.join(tmpDir, "build");
407+
const files = fs.readdirSync(buildDir);
408+
const libFile = files.find(
409+
(f) => f.startsWith(id) && (f.endsWith(".dll") || f.endsWith(".so") || f.endsWith(".dylib")),
410+
);
411+
if (!libFile) throw new Error("Shared library not found after CMake compilation.");
412+
413+
return new Uint8Array(fs.readFileSync(path.join(buildDir, libFile)));
414+
} finally {
415+
fs.rmSync(tmpDir, { recursive: true, force: true });
416+
}
417+
}

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

Lines changed: 104 additions & 10 deletions

0 commit comments

Comments
 (0)