247 lines
8.6 KiB
Rust
247 lines
8.6 KiB
Rust
//! Typed live reference domains; parameter IDs and opaque historical captures are separate.
|
|
use super::*;
|
|
use crate::{Element, Id, Project, SharedBody};
|
|
use std::collections::BTreeSet;
|
|
|
|
pub fn expression_properties(expression: &Expr) -> BTreeSet<Id> {
|
|
let mut found = BTreeSet::new();
|
|
walk(expression, &mut |node| {
|
|
if let Expr::Property(id) = node {
|
|
found.insert(*id);
|
|
}
|
|
});
|
|
found
|
|
}
|
|
pub fn expression_definitions(expression: &Expr) -> BTreeSet<Id> {
|
|
let mut found = BTreeSet::new();
|
|
walk(expression, &mut |node| match node {
|
|
Expr::Quantity { unit, .. } => {
|
|
found.insert(unit.local());
|
|
}
|
|
Expr::Call { definition, .. } => {
|
|
found.insert(definition.local());
|
|
}
|
|
Expr::AsKind { kind, .. } => {
|
|
found.insert(kind.local());
|
|
}
|
|
_ => {}
|
|
});
|
|
found
|
|
}
|
|
pub(crate) fn walk<'a>(expression: &'a Expr, visit: &mut impl FnMut(&'a Expr)) {
|
|
let mut pending = vec![expression];
|
|
while let Some(node) = pending.pop() {
|
|
visit(node);
|
|
match node {
|
|
Expr::List(items) => pending.extend(items.iter().rev()),
|
|
Expr::Unary { operand, .. } => pending.push(operand),
|
|
Expr::Binary { left, right, .. } => {
|
|
pending.push(right);
|
|
pending.push(left);
|
|
}
|
|
Expr::If {
|
|
condition,
|
|
when_true,
|
|
when_false,
|
|
} => {
|
|
pending.push(when_false);
|
|
pending.push(when_true);
|
|
pending.push(condition);
|
|
}
|
|
Expr::Call { arguments, .. } => pending.extend(arguments.values().rev()),
|
|
Expr::Sum { values, initial } => {
|
|
pending.push(values);
|
|
pending.push(initial);
|
|
}
|
|
Expr::Power { base, .. } => pending.push(base),
|
|
Expr::AsKind { value, .. } => pending.push(value),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
fn walk_mut(expression: &mut Expr, visit: &mut impl FnMut(&mut Expr)) {
|
|
let mut pending = vec![expression];
|
|
while let Some(node) = pending.pop() {
|
|
visit(node);
|
|
match node {
|
|
Expr::List(items) => pending.extend(items.iter_mut()),
|
|
Expr::Unary { operand, .. } => pending.push(operand),
|
|
Expr::Binary { left, right, .. } => {
|
|
pending.push(left);
|
|
pending.push(right);
|
|
}
|
|
Expr::If {
|
|
condition,
|
|
when_true,
|
|
when_false,
|
|
} => {
|
|
pending.push(condition);
|
|
pending.push(when_true);
|
|
pending.push(when_false);
|
|
}
|
|
Expr::Call { arguments, .. } => pending.extend(arguments.values_mut()),
|
|
Expr::Sum { values, initial } => {
|
|
pending.push(values);
|
|
pending.push(initial);
|
|
}
|
|
Expr::Power { base, .. } => pending.push(base),
|
|
Expr::AsKind { value, .. } => pending.push(value),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
pub fn element_model_references(element: &Element) -> BTreeSet<Id> {
|
|
let mut result = BTreeSet::new();
|
|
let mut binding = |value: &Binding| {
|
|
if let Binding::Expression(expression) = value {
|
|
result.extend(expression_properties(expression));
|
|
}
|
|
};
|
|
match element.shared() {
|
|
Some(SharedBody::EngineeringProperty { base, .. }) => binding(base),
|
|
Some(SharedBody::AnalysisCase { overrides, .. }) => {
|
|
for value in overrides.values() {
|
|
binding(value);
|
|
}
|
|
}
|
|
Some(SharedBody::AnalysisCheck { predicate, .. }) => binding(predicate),
|
|
_ => {}
|
|
}
|
|
match element.shared() {
|
|
Some(SharedBody::EngineeringProperty { subject, .. }) => {
|
|
result.insert(subject.id());
|
|
}
|
|
Some(SharedBody::AnalysisCase {
|
|
subject, overrides, ..
|
|
}) => {
|
|
result.extend(subject.map(AnalysisSubject::id));
|
|
result.extend(overrides.keys());
|
|
}
|
|
Some(SharedBody::AnalysisCheck {
|
|
subject,
|
|
documented_constraint,
|
|
..
|
|
}) => {
|
|
result.extend(subject.map(AnalysisSubject::id));
|
|
result.extend(documented_constraint);
|
|
}
|
|
_ => {}
|
|
}
|
|
result
|
|
}
|
|
fn remap_ref(reference: &mut DefinitionRef, old: Id, new: Id) {
|
|
let DefinitionRef::Local(id) = reference;
|
|
if *id == old {
|
|
*id = new;
|
|
}
|
|
}
|
|
fn remap_type(value: &mut AnalysisType, old: Id, new: Id) {
|
|
let (AnalysisType::Scalar(scalar) | AnalysisType::List(scalar)) = value;
|
|
if let ScalarType::Quantity(reference) = scalar {
|
|
remap_ref(reference, old, new);
|
|
}
|
|
}
|
|
fn remap_binding(value: &mut Binding, old: Id, new: Id) {
|
|
if let Binding::Expression(expression) = value {
|
|
walk_mut(expression, &mut |node| match node {
|
|
Expr::Property(id) if *id == old => *id = new,
|
|
Expr::Quantity { unit, .. } => remap_ref(unit, old, new),
|
|
Expr::Call { definition, .. } => remap_ref(definition, old, new),
|
|
Expr::AsKind { kind, .. } => remap_ref(kind, old, new),
|
|
_ => {} // Parameter nodes and argument keys are scoped, never global.
|
|
});
|
|
}
|
|
}
|
|
fn remap_subject(subject: &mut AnalysisSubject, old: Id, new: Id) {
|
|
let (AnalysisSubject::Element(id) | AnalysisSubject::Relation(id)) = subject;
|
|
if *id == old {
|
|
*id = new;
|
|
}
|
|
}
|
|
|
|
/// Check before any mutation so a map-key collision never silently discards a binding.
|
|
pub(crate) fn check_remap(project: &Project, old: Id, new: Id) -> Result<(), AnalysisError> {
|
|
if old != new {
|
|
for element in project.elements.values() {
|
|
if let Some(SharedBody::AnalysisCase { overrides, .. }) = element.shared()
|
|
&& overrides.contains_key(&old)
|
|
&& overrides.contains_key(&new)
|
|
{
|
|
return Err(AnalysisError::new(
|
|
element.id,
|
|
"overrides",
|
|
"analysis_reference_collision",
|
|
"Reidentification would merge two distinct override keys.",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn remap_project(project: &mut Project, old: Id, new: Id) {
|
|
for element in project.elements.values_mut() {
|
|
match element.shared_mut() {
|
|
Some(SharedBody::EngineeringProperty {
|
|
subject,
|
|
value_type,
|
|
display_unit,
|
|
base,
|
|
}) => {
|
|
remap_subject(subject, old, new);
|
|
remap_type(value_type, old, new);
|
|
if let Some(reference) = display_unit {
|
|
remap_ref(reference, old, new);
|
|
}
|
|
remap_binding(base, old, new);
|
|
}
|
|
Some(SharedBody::AnalysisCase {
|
|
subject, overrides, ..
|
|
}) => {
|
|
if let Some(subject) = subject {
|
|
remap_subject(subject, old, new);
|
|
}
|
|
if old != new
|
|
&& let Some(value) = overrides.remove(&old)
|
|
{
|
|
overrides.insert(new, value);
|
|
}
|
|
for value in overrides.values_mut() {
|
|
remap_binding(value, old, new);
|
|
}
|
|
}
|
|
Some(SharedBody::AnalysisCheck {
|
|
subject,
|
|
documented_constraint,
|
|
predicate,
|
|
}) => {
|
|
if let Some(subject) = subject {
|
|
remap_subject(subject, old, new);
|
|
}
|
|
if *documented_constraint == Some(old) {
|
|
*documented_constraint = Some(new);
|
|
}
|
|
remap_binding(predicate, old, new);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
for definition in project.definitions.values_mut() {
|
|
match &mut definition.body {
|
|
DefinitionBody::QuantityKind(QuantityKindDefinition {
|
|
algebra: QuantityAlgebra::Affine { difference },
|
|
..
|
|
}) => remap_ref(difference, old, new),
|
|
DefinitionBody::Unit(unit) => remap_ref(&mut unit.kind, old, new),
|
|
DefinitionBody::Calculation(calculation) => {
|
|
for parameter in &mut calculation.parameters {
|
|
remap_type(&mut parameter.value_type, old, new);
|
|
}
|
|
remap_type(&mut calculation.result, old, new);
|
|
remap_binding(&mut calculation.body, old, new);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|