Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ High-level release summary by `0.x` line. Patch releases are rolled up into thei
- Sharpened the editor and LSP experience with separate interactive/strict work lanes, binary library caching, and cleaner import diagnostics.
- Restructured the developer CLI around target-first `cargo xtask` commands and moved CI onto the prebuilt dev container image.
- Added an eFMI/GALEC Algorithm Code export path (`--target galec`) that emits schema-valid eFMU and eFMI Production Code containers, with a dedicated GALEC (`.alg`) language server wired into VS Code and an embedded-C equivalence harness.
- Extended the GALEC projection to Kalman-class estimators: matrix algebra (matrix products, `transpose`, `identity`), `initial equation` → `Startup` lowering with manifest-start mirroring, and the `Matrices.solve` → `solveLinearEquations` mapping with declared-by-construction escape sets (per-method manifest Signals; the embedded C methods return the ErrorSignalStatus word).
- Added a non-eFMI embedded Rust export (`--target embedded-rust-galec`): a `#![no_std]` crate root generated by a walking minijinja template over a language-neutral GALEC block context, with `Result<(), Signals>` methods and a C↔Rust↔reference Kalman-filter equivalence test (`examples/models/QuadrotorAltitudeKF.mo`).
- Moved every GALEC rendering onto walking templates over the language-neutral GALEC template IR (SPEC_0034 D16/D17): the `.alg` text itself (byte-identical to the typed printer, pinned by a parity test) and the embedded C track (the typed C printer was deleted; adding a target language is now adding a template).
- Added neural-ODE and optimization support and unified the simulation session integration across the CLI, LSP, and wasm surfaces.
- Continued improving MSL trace parity and performance with an event-driven baseline ratchet published as a release asset, and cut CI time by sharding the MSL gate behind a build-once Nix path.

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 90 additions & 28 deletions crates/rumoca-compile/src/galec_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,61 @@ pub fn render_galec_c_export(
})
}

/// The language-neutral, template-walkable GALEC block context (SPEC_0034
/// D16): serialized statement/expression trees the target's minijinja
/// template walks with recursive macros — exactly how the generic IR
/// targets consume their serialized IR. One context serves every
/// GALEC-template target (`embedded-rust-galec` today); each language is a
/// template, not a printer.
#[derive(Debug, Clone)]
pub struct GalecBlockContext {
/// Serialized `rumoca_galec_codegen::template_ir` tree: collision-checked
/// base identifiers, 0-based subscripts, T7-strict Real literal text,
/// kind-tagged expression nodes, dual whole-array value/element forms.
pub context: serde_json::Value,
}

/// Render one GALEC-template target file (path or code template) against a
/// [`GalecBlockContext`]-shaped JSON context under the standard codegen
/// environment ([`rumoca_phase_codegen::render_template_with_json_context`]),
/// so walking templates get the exact filter/function toolbox the generic
/// IR targets have (D16).
///
/// # Errors
///
/// [`GalecExportError::CTemplate`] on render failure (strict-undefined
/// misses included).
pub fn render_galec_block_template(
context: &serde_json::Value,
template: &str,
) -> Result<String, GalecExportError> {
rumoca_phase_codegen::render_template_with_json_context(context, template).map_err(|error| {
GalecExportError::CTemplate {
detail: format!("render GALEC block template: {error}"),
}
})
}

/// Build the template-walkable GALEC block context for a compiled model —
/// the same projection every export shares, serialized through
/// `rumoca_galec_codegen::galec_template_context` (D16).
///
/// # Errors
///
/// [`GalecExportError::Projection`] with all collected projection
/// diagnostics, or [`GalecExportError::Render`] for validator/serializer
/// failures on the validated package (`ET018`/`ET022`/`ET023`).
pub fn galec_block_context(
dae: &Dae,
flat: &FlatModel,
model_name: &str,
) -> Result<GalecBlockContext, GalecExportError> {
let package = lower_package(dae, flat, model_name)?;
Ok(GalecBlockContext {
context: rumoca_galec_codegen::galec_template_context(&package, model_name)?,
})
}

// ===========================================================================
// Switch-dispatch packaging plan (contract §9 WI-5)
// ===========================================================================
Expand Down Expand Up @@ -541,6 +596,8 @@ pub const GALEC_PRODUCTION_TARGET: &str = "galec-production";
/// (D10: the prelude is a fixed contract with the typed C printer; duplicating
/// it would be drift-prone).
pub const EMBEDDED_C_GALEC_TARGET: &str = "embedded-c-galec";
/// The non-eFMI Rust track (SPEC_0034 GAL-030/D15).
pub const EMBEDDED_RUST_GALEC_TARGET: &str = "embedded-rust-galec";
/// Shared C-layout template names (`[[files]]` of the builtin target).
const HEADER_TEMPLATE: &str = "model.h.jinja";
const SOURCE_TEMPLATE: &str = "model.c.jinja";
Expand Down Expand Up @@ -572,6 +629,18 @@ pub const EMBEDDED_C_GALEC_CONFORMANCE_LINES: &[&str] = &[
];
pub const EMBEDDED_C_GALEC_CONFORMANCE_SUMMARY: &str = "NOT an eFMI Production Code container.";

/// The `embedded-rust-galec` conformance claim (GAL-030): the Beta-1
/// ProductionCode XSD restricts `language` to C/C++, so a Rust artifact can
/// never claim the Production Code rung — the honest self-description is
/// baked into the generated file's doc comment. Comment-safe.
pub const EMBEDDED_RUST_GALEC_CONFORMANCE_LINES: &[&str] = &[
"NOT an eFMI Production Code container: the eFMI Beta-1 ProductionCode",
"schema restricts `language` to C/C++, so no eFMU manifest mapping",
"exists for this artifact. Generated by rumoca --target embedded-rust-galec.",
];
pub const EMBEDDED_RUST_GALEC_CONFORMANCE_SUMMARY: &str =
"NOT an eFMI Production Code container (Rust is outside the Beta-1 PC schema).";

/// Resolve the shared builtin C-layout template bundle. Absence is a
/// build-system invariant break (the templates are embedded at compile
/// time), reported loudly per SPEC_0008.
Expand All @@ -598,21 +667,23 @@ fn render_c_layout_template(
.map_err(|error| GalecExportError::CTemplate {
detail: format!("{error:#}"),
})?;
let mut env = minijinja::Environment::new();
env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
register_manifest_filters(&mut env);
env.render_str(
&source,
minijinja::context! {
conformance_header => minijinja::context! {
lines => conformance_lines,
summary => conformance_summary,
},
..minijinja::Value::from_serialize(c_context)
},
)
.map_err(|error| GalecExportError::CTemplate {
detail: format!("render '{template}': {error}"),
// D16/D17: the C templates are walking templates over the GALEC block
// context; render under the standard codegen environment so they get
// the same filter/function toolbox (`fail`, …) as every other walker.
let mut context = c_context.clone();
if let serde_json::Value::Object(map) = &mut context {
map.insert(
"conformance_header".to_owned(),
serde_json::json!({
"lines": conformance_lines,
"summary": conformance_summary,
}),
);
}
rumoca_phase_codegen::render_template_with_json_context(&context, &source).map_err(|error| {
GalecExportError::CTemplate {
detail: format!("render '{template}': {error}"),
}
})
}

Expand Down Expand Up @@ -655,7 +726,10 @@ pub fn render_galec_c_files_from_context(
pub fn is_galec_target(target: &str) -> bool {
matches!(
target,
GALEC_TARGET | GALEC_PRODUCTION_TARGET | EMBEDDED_C_GALEC_TARGET
GALEC_TARGET
| GALEC_PRODUCTION_TARGET
| EMBEDDED_C_GALEC_TARGET
| EMBEDDED_RUST_GALEC_TARGET
)
}

Expand Down Expand Up @@ -728,18 +802,6 @@ pub fn render_galec_sources(
})
}

/// Register the eFMI manifest render filters (contract §3b) on a bare
/// minijinja environment: `xml_escape` (autoescape is OFF, so every text
/// value is escaped explicitly) and `xs_double` (raw `f64` → valid `xs:double`
/// lexical). Reuses the filter functions re-exported by `rumoca-galec-codegen` so the
/// guarantee is defined once and shared by every manifest render env.
pub(crate) fn register_manifest_filters(env: &mut minijinja::Environment<'_>) {
env.add_filter("xml_escape", |text: String| {
rumoca_galec_codegen::xml_escape(&text)
});
env.add_filter("xs_double", rumoca_galec_codegen::xs_double);
}

/// Build the [`ScalarTypeMap`] for a compiled model from Flat-side declared
/// types (module docs). Generated condition/`__pre__` variables stay absent
/// on purpose — the projection's `classify` fallbacks own them — and any
Expand Down
11 changes: 7 additions & 4 deletions crates/rumoca-compile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,14 @@ pub mod codegen {
pub mod galec {
pub use crate::galec_api::{
EMBEDDED_C_GALEC_CONFORMANCE_LINES, EMBEDDED_C_GALEC_CONFORMANCE_SUMMARY,
EMBEDDED_C_GALEC_TARGET, GALEC_PRODUCTION_TARGET, GALEC_TARGET, GalecCExport,
GalecExportError, GalecPackagingPlan, GalecSources, PRODUCTION_CONFORMANCE_LINES,
EMBEDDED_C_GALEC_TARGET, EMBEDDED_RUST_GALEC_CONFORMANCE_LINES,
EMBEDDED_RUST_GALEC_CONFORMANCE_SUMMARY, EMBEDDED_RUST_GALEC_TARGET,
GALEC_PRODUCTION_TARGET, GALEC_TARGET, GalecBlockContext, GalecCExport, GalecExportError,
GalecPackagingPlan, GalecSources, PRODUCTION_CONFORMANCE_LINES,
PRODUCTION_CONFORMANCE_SUMMARY, build_scalar_type_map, dae_for_galec_projection,
is_galec_target, plan_galec_export, plan_galec_production_export, render_galec_c_export,
render_galec_c_files_from_context, render_galec_sources,
galec_block_context, is_galec_target, plan_galec_export, plan_galec_production_export,
render_galec_block_template, render_galec_c_export, render_galec_c_files_from_context,
render_galec_sources,
};
pub use rumoca_galec_codegen::{GalecInput, GalecOptions, GalecTargetError, ScalarTypeMap};
pub use rumoca_ir_galec::ast::ScalarType;
Expand Down
3 changes: 3 additions & 0 deletions crates/rumoca-galec-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ repository.workspace = true
rumoca-core = { workspace = true }
rumoca-ir-dae = { workspace = true }
rumoca-ir-galec = { workspace = true }
# D17: the `.alg` text renders from the embedded walking template
# (src/templates/alg.jinja) over the language-neutral template IR.
minijinja = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha1 = { workspace = true }
Expand Down
30 changes: 16 additions & 14 deletions crates/rumoca-galec-codegen/src/admissibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ pub fn check_admissibility(input: &GalecInput<'_>) -> Result<AdmittedClock, Vec<
let mut errors = Vec::new();
check_metadata(dae, &mut errors);
check_continuous(dae, &mut errors);
check_initialization(dae, &mut errors);
check_external_functions(dae, &mut errors);
check_runtime_events(dae, &mut errors);
check_dimensions(dae, &mut errors);
Expand Down Expand Up @@ -70,24 +69,27 @@ fn check_continuous(dae: &Dae, errors: &mut Vec<GalecTargetError>) {
}
}

/// (g) No initial equations (GAL-025 wording): `Startup` is built from
/// manifest `start` values only, so a non-empty initialization partition
/// would be silently ignored rather than lowered — reject it up front.
fn check_initialization(dae: &Dae, errors: &mut Vec<GalecTargetError>) {
let equations = dae.initialization.equations.len();
let structured_families = dae.initialization.structured_equations.len();
if equations > 0 || structured_families > 0 {
errors.push(GalecTargetError::InitialEquations {
equations,
structured_families,
});
}
}
// (g) — removed: the initialization partition now lowers into `Startup`
// (GAL-028, `crate::lower::initialization`); un-lowerable forms fail there
// with stable `unsupported-feature:` diagnostics instead of a blanket ET021.

/// (b) No external functions (GAL-025 wording), one error per function so
/// the report names every offender.
///
/// Exemption (D13): the `Modelica.Math.Matrices` namespace maps by name to
/// GALEC catalog builtins (`solve` → `solveLinearEquations`) instead of
/// calling its LAPACK-external MSL bodies, so referencing it must not
/// reject up front. A `Matrices` function that does NOT map still fails
/// precisely during lowering (its external callee is never inlined).
fn check_external_functions(dae: &Dae, errors: &mut Vec<GalecTargetError>) {
for function in dae.symbols.functions.values() {
if function
.name
.as_str()
.starts_with("Modelica.Math.Matrices.")
{
continue;
}
if let Some(external) = &function.external {
errors.push(GalecTargetError::ExternalFunction {
function: function.name.as_str().to_owned(),
Expand Down
2 changes: 1 addition & 1 deletion crates/rumoca-galec-codegen/src/c_mangle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ impl CNameTable {
}
}

fn literal_dimensions(dimensions: &[Dimension]) -> Result<Vec<i64>, GalecTargetError> {
pub(crate) fn literal_dimensions(dimensions: &[Dimension]) -> Result<Vec<i64>, GalecTargetError> {
dimensions
.iter()
.map(|dimension| match dimension {
Expand Down
Loading
Loading