use crate::code::ASTMetadata;
use crate::diff::{ASTDiff, ASTMappingOperation, COST_DELETE, COST_INSERT, COST_UPDATE};
pub fn operation_cost(
operation: &ASTMappingOperation,
subtree_size: usize,
owned_text_changed: bool,
) -> u64 {
match operation {
ASTMappingOperation::MatchButNotIdentical if owned_text_changed => COST_UPDATE,
ASTMappingOperation::Identical
| ASTMappingOperation::MatchButNotIdentical
| ASTMappingOperation::NotYetSet => 0,
ASTMappingOperation::Update => COST_UPDATE,
ASTMappingOperation::Delete => COST_DELETE,
ASTMappingOperation::Insert => COST_INSERT,
ASTMappingOperation::DeleteWithChildren => COST_DELETE * subtree_size as u64,
ASTMappingOperation::InsertWithChildren => COST_INSERT * subtree_size as u64,
}
}
pub fn diff_cost(
diff: &ASTDiff,
before_metadata: &ASTMetadata,
after_metadata: &ASTMetadata,
) -> u64 {
diff.mapping
.iter()
.map(|(&(before_id, after_id), m)| {
let subtree_size = match m.operation {
ASTMappingOperation::DeleteWithChildren => before_metadata
.node_to_subtree_size
.get(&before_id)
.copied()
.unwrap_or(1),
ASTMappingOperation::InsertWithChildren => after_metadata
.node_to_subtree_size
.get(&after_id)
.copied()
.unwrap_or(1),
_ => 1,
};
let owned_text_hash = |metadata: &ASTMetadata, id: usize| {
metadata.node_info.get(&id).map(|info| info.owned_text_hash)
};
let owned_text_changed = owned_text_hash(before_metadata, before_id)
!= owned_text_hash(after_metadata, after_id);
operation_cost(&m.operation, subtree_size, owned_text_changed)
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diff::{ASTMapping, ASTMappingReason};
fn metadata_with_sizes(sizes: &[(usize, usize)]) -> ASTMetadata {
let mut metadata = ASTMetadata::default();
for &(id, size) in sizes {
metadata.node_to_subtree_size.insert(id, size);
}
metadata
}
fn mapping(operation: ASTMappingOperation) -> ASTMapping {
ASTMapping {
cost: 0,
operation,
reason: ASTMappingReason::APTED("test"),
}
}
#[test]
fn identical_and_unchanged_match_but_not_identical_cost_nothing() {
assert_eq!(
operation_cost(&ASTMappingOperation::Identical, 50, false),
0
);
assert_eq!(
operation_cost(&ASTMappingOperation::MatchButNotIdentical, 50, false),
0
);
}
#[test]
fn single_node_operations_cost_one_regardless_of_subtree_size() {
assert_eq!(
operation_cost(&ASTMappingOperation::Update, 50, false),
COST_UPDATE
);
assert_eq!(
operation_cost(&ASTMappingOperation::Delete, 50, false),
COST_DELETE
);
assert_eq!(
operation_cost(&ASTMappingOperation::Insert, 50, false),
COST_INSERT
);
}
#[test]
fn with_children_operations_scale_by_subtree_size() {
assert_eq!(
operation_cost(&ASTMappingOperation::DeleteWithChildren, 7, false),
7 * COST_DELETE
);
assert_eq!(
operation_cost(&ASTMappingOperation::InsertWithChildren, 3, false),
3 * COST_INSERT
);
}
#[test]
fn diff_cost_sums_single_node_entries_without_double_counting() {
let mut diff = ASTDiff::default();
diff.add_mapping(1, 1, mapping(ASTMappingOperation::Identical));
diff.add_mapping(2, 2, mapping(ASTMappingOperation::Update));
diff.add_mapping(3, 0, mapping(ASTMappingOperation::Delete));
diff.add_mapping(0, 4, mapping(ASTMappingOperation::Insert));
let before_meta = ASTMetadata::default();
let after_meta = ASTMetadata::default();
assert_eq!(
diff_cost(&diff, &before_meta, &after_meta),
COST_UPDATE + COST_DELETE + COST_INSERT
);
}
#[test]
fn diff_cost_scales_with_children_entries_by_metadata_subtree_size() {
let mut diff = ASTDiff::default();
diff.add_mapping(10, 0, mapping(ASTMappingOperation::DeleteWithChildren));
diff.add_mapping(0, 20, mapping(ASTMappingOperation::InsertWithChildren));
let before_meta = metadata_with_sizes(&[(10, 6)]);
let after_meta = metadata_with_sizes(&[(20, 4)]);
assert_eq!(
diff_cost(&diff, &before_meta, &after_meta),
6 * COST_DELETE + 4 * COST_INSERT
);
}
#[test]
fn match_but_not_identical_charges_for_a_node_s_own_changed_text() {
assert_eq!(
operation_cost(&ASTMappingOperation::MatchButNotIdentical, 1, true),
COST_UPDATE
);
assert_eq!(
operation_cost(&ASTMappingOperation::MatchButNotIdentical, 1, false),
0
);
assert_eq!(operation_cost(&ASTMappingOperation::Identical, 1, true), 0);
assert_eq!(
operation_cost(&ASTMappingOperation::Update, 1, true),
COST_UPDATE
);
}
#[test]
fn diff_cost_charges_a_gap_owning_matched_pair() {
let node = |owned_text_hash: u64| crate::code::ASTNodeMetadata {
owned_text_hash,
..crate::code::ASTNodeMetadata::new("AttValue".to_string(), String::new(), vec![], 0, 0)
};
let mut before_meta = ASTMetadata::default();
let mut after_meta = ASTMetadata::default();
before_meta.node_info.insert(1, node(0xABC));
after_meta.node_info.insert(2, node(0xDEF));
let mut diff = ASTDiff::default();
diff.add_mapping(1, 2, mapping(ASTMappingOperation::MatchButNotIdentical));
assert_eq!(diff_cost(&diff, &before_meta, &after_meta), COST_UPDATE);
after_meta.node_info.insert(2, node(0xABC));
assert_eq!(diff_cost(&diff, &before_meta, &after_meta), 0);
}
}