Files
ARCADIA-Studio/crates/oa-core/src/analysis/references.rs
T

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);
}
_ => {}
}
}
}