diff --git a/Cargo.lock b/Cargo.lock index bc1f00d..4d4565f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ name = "bin" version = "0.1.0" dependencies = [ "matching", + "merge", "model", "parsing", "utils", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index c6bd12a..547256d 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +merge = { path = "../merge" } model = { path = "../model" } parsing = { path = "../parsing" } matching = { path = "../matching" } diff --git a/bin/src/main.rs b/bin/src/main.rs index 27c5cee..e706af3 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -1,4 +1,6 @@ use matching::ordered_tree_matching; +use merge::merge; +use model::CSTNode; fn main() { let base = parsing::parse_string( @@ -31,10 +33,31 @@ fn main() { ) .unwrap(); - let result = ordered_tree_matching(&left, &base); + let matchings_left_base = ordered_tree_matching(&left, &base); + let matchings_right_base = ordered_tree_matching(&right, &base); + let matchings_lef_right = ordered_tree_matching(&left, &right); + let result = merge( + &base, + &left, + &right, + &matchings_left_base, + &matchings_right_base, + &matchings_lef_right, + ); - // result.into_iter().for_each(|(pair, matching)| { - // println!("{:#?}", pair); - // println!("{:?}", matching); - // }); + println!("{:#?}", pretty_print(result)) +} + +pub fn pretty_print(node: CSTNode) -> String { + match node { + CSTNode::Terminal { value, .. } => value, + CSTNode::NonTerminal { children, .. } => { + children.iter().fold(String::new(), |acc, current| { + let mut result = acc.to_owned(); + result.push_str(" "); + result.push_str(&pretty_print(current.clone())); + result + }) + } + } } diff --git a/matching/src/matching.rs b/matching/src/matching.rs index ca9c52c..90e3426 100644 --- a/matching/src/matching.rs +++ b/matching/src/matching.rs @@ -4,4 +4,5 @@ use model::CSTNode; pub struct Matching<'a> { pub matching_node: &'a CSTNode, pub score: usize, + pub is_perfect_match: bool, } diff --git a/matching/src/matching_entry.rs b/matching/src/matching_entry.rs index bb74cdf..d3424c8 100644 --- a/matching/src/matching_entry.rs +++ b/matching/src/matching_entry.rs @@ -1,10 +1,14 @@ #[derive(Clone, Debug, PartialEq, Eq)] pub struct MatchingEntry { pub score: usize, + pub is_perfect_match: bool, } impl MatchingEntry { - pub fn with_score(score: usize) -> Self { - return MatchingEntry { score }; + pub fn new(score: usize, is_perfect_match: bool) -> Self { + return MatchingEntry { + score, + is_perfect_match, + }; } } diff --git a/matching/src/matchings.rs b/matching/src/matchings.rs index 1288701..26beb62 100644 --- a/matching/src/matchings.rs +++ b/matching/src/matchings.rs @@ -31,6 +31,7 @@ impl Matchings { Matching { matching_node, score: matching.score, + is_perfect_match: matching.is_perfect_match, } }) } @@ -64,13 +65,14 @@ mod tests { let mut matchings = HashMap::new(); matchings.insert( UnorderedPair(a_node.clone(), a_node.clone()), - MatchingEntry::with_score(1), + MatchingEntry::new(1, true), ); assert_eq!( Some(Matching { matching_node: &a_node, - score: 1 + score: 1, + is_perfect_match: true }), Matchings::new(matchings).find_matching_for(&a_node) ) diff --git a/matching/src/ordered_tree_matching.rs b/matching/src/ordered_tree_matching.rs index 7a81fe6..9043435 100644 --- a/matching/src/ordered_tree_matching.rs +++ b/matching/src/ordered_tree_matching.rs @@ -100,9 +100,10 @@ fn ordered_tree_matching_helper( } } - let matching = MatchingEntry { - score: matrix_m[m][n] + root_matching, - }; + let matching = MatchingEntry::new( + matrix_m[m][n] + root_matching, + 2 * matrix_m[m][n] / (m + n) == 1, + ); let mut result = HashMap::new(); result.insert( UnorderedPair::new(left.to_owned(), right.to_owned()), @@ -126,11 +127,10 @@ fn ordered_tree_matching_helper( }, ) => { let mut result = HashMap::new(); + let is_perfetch_match = kind_left == kind_right && value_left == value_right; result.insert( UnorderedPair::new(left.to_owned(), right.to_owned()), - MatchingEntry { - score: (kind_left == kind_right && value_left == value_right).into(), - }, + MatchingEntry::new(is_perfetch_match.into(), is_perfetch_match), ); result } @@ -138,7 +138,7 @@ fn ordered_tree_matching_helper( let mut result = HashMap::new(); result.insert( UnorderedPair::new(left.to_owned(), right.to_owned()), - MatchingEntry { score: 0 }, + MatchingEntry::new(0, false), ); result } @@ -164,7 +164,7 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(1)), + Some(&MatchingEntry::new(1, true)), matchings.get_matching_entry(left, right) ) } @@ -183,7 +183,7 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(0)), + Some(&MatchingEntry::new(0, false)), matchings.get_matching_entry(left, right) ) } @@ -202,7 +202,7 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(0)), + Some(&MatchingEntry::new(0, false)), matchings.get_matching_entry(left, right) ) } @@ -221,7 +221,7 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(0)), + Some(&MatchingEntry::new(0, false)), matchings.get_matching_entry(left, right) ) } @@ -244,7 +244,7 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(1)), + Some(&MatchingEntry::new(1, true)), matchings.get_matching_entry(child.clone(), child) ) } @@ -297,7 +297,31 @@ mod tests { let matchings = ordered_tree_matching(&left, &right); assert_eq!( - Some(&MatchingEntry::with_score(2)), + Some(&MatchingEntry::new(2, false)), + matchings.get_matching_entry(left, right) + ) + } + + #[test] + fn perfect_matching_deep_nodes() { + let common_child = CSTNode::Terminal { + kind: "kind_b".into(), + value: "value_b".into(), + }; + + let left = CSTNode::NonTerminal { + kind: "kind_a".to_owned(), + children: vec![common_child.clone()], + }; + let right = CSTNode::NonTerminal { + kind: "kind_a".to_owned(), + children: vec![common_child.clone()], + }; + + let matchings = ordered_tree_matching(&left, &right); + + assert_eq!( + Some(&MatchingEntry::new(2, true)), matchings.get_matching_entry(left, right) ) } diff --git a/merge/src/lib.rs b/merge/src/lib.rs index 4fa1242..29c1981 100644 --- a/merge/src/lib.rs +++ b/merge/src/lib.rs @@ -1,12 +1,16 @@ -use model::CSTNode; +use std::borrow::BorrowMut; -#[derive(Debug, Eq, PartialEq)] -pub enum MergeResultNode { - Conflict(CSTNode), - Success(CSTNode), -} +use matching::Matchings; +use model::CSTNode; -pub fn merge(base: &CSTNode, left: &CSTNode, right: &CSTNode) -> MergeResultNode { +pub fn merge( + base: &CSTNode, + left: &CSTNode, + right: &CSTNode, + base_left_matchings: &Matchings, + base_right_matchings: &Matchings, + left_right_matchings: &Matchings, +) -> CSTNode { match (base, left, right) { ( CSTNode::Terminal { @@ -23,29 +27,104 @@ pub fn merge(base: &CSTNode, left: &CSTNode, right: &CSTNode) -> MergeResultNode ) => { // Unchanged if value_left == value_base && value_right == value_base { - MergeResultNode::Success(base.to_owned()) + base.to_owned() // Changed in both } else if value_left != value_base && value_right != value_base { match diffy::merge(&value_base, &value_left, &value_right) { - Ok(value) => MergeResultNode::Success(CSTNode::Terminal { + Ok(value) => CSTNode::Terminal { kind: kind.to_owned(), value, - }), - Err(value) => MergeResultNode::Conflict(CSTNode::Terminal { + }, + Err(value) => CSTNode::Terminal { kind: kind.to_owned(), value, - }), + }, } // Only left changed } else if value_left != value_base { - MergeResultNode::Success(left.to_owned()) + left.to_owned() // Only right changed } else { - MergeResultNode::Success(right.to_owned()) + right.to_owned() } } - (CSTNode::NonTerminal { .. }, CSTNode::NonTerminal { .. }, CSTNode::NonTerminal { .. }) => { - todo!() + ( + CSTNode::NonTerminal { + kind, + children: base_children, + }, + CSTNode::NonTerminal { + children: children_left, + .. + }, + CSTNode::NonTerminal { + children: children_right, + .. + }, + ) => { + let mut result_children = vec![]; + + // Mutually modified + let mut mutually_modified_children: Vec = base_children + .iter() + .map(|node| { + return ( + node, + base_left_matchings.find_matching_for(node), + base_right_matchings.find_matching_for(node), + ); + }) + .filter(|(_, left_match, right_match)| { + return left_match.is_some() && right_match.is_some(); + }) + .map(|(base, left_match, right_match)| { + return merge( + &base, + left_match.unwrap().matching_node, + right_match.unwrap().matching_node, + &base_left_matchings, + &base_right_matchings, + &left_right_matchings, + ); + }) + .collect(); + + result_children.append(&mut mutually_modified_children); + + // Nodes added only in left + result_children.append( + children_left + .iter() + .filter(|left_child| { + return base_left_matchings.find_matching_for(left_child).is_none(); + // && left_right_matchings.find_matching_for(left_child).is_none(); + }) + .map(|node| node.to_owned()) + .collect::>() + .borrow_mut(), + ); + + // Nodes added only in right + result_children.append( + children_right + .iter() + .filter(|right_child| { + return base_right_matchings + .find_matching_for(right_child) + .is_none(); + // && left_right_matchings + // .find_matching_for(right_child) + // .is_none(); + }) + .map(|node| node.to_owned()) + .collect::>() + .borrow_mut(), + ); + + CSTNode::NonTerminal { + kind: kind.to_owned(), + children: result_children, + } } (_, _, _) => panic!("Can not merge Terminal with Non-Terminal"), } @@ -53,9 +132,12 @@ pub fn merge(base: &CSTNode, left: &CSTNode, right: &CSTNode) -> MergeResultNode #[cfg(test)] mod tests { + use std::vec; + + use matching::{ordered_tree_matching, Matchings}; use model::CSTNode; - use crate::{merge, MergeResultNode}; + use crate::merge; #[test] fn if_i_am_merging_three_unchanged_nodes_it_is_a_success() { @@ -63,7 +145,17 @@ mod tests { kind: "kind".into(), value: "value".into(), }; - assert_eq!(merge(&node, &node, &node), MergeResultNode::Success(node)) + assert_eq!( + merge( + &node, + &node, + &node, + &Matchings::empty(), + &Matchings::empty(), + &Matchings::empty() + ), + node + ) } #[test] @@ -82,11 +174,18 @@ mod tests { }; assert_eq!( - merge(&base, &left, &right), - MergeResultNode::Success(CSTNode::Terminal { + merge( + &base, + &left, + &right, + &Matchings::empty(), + &Matchings::empty(), + &Matchings::empty() + ), + CSTNode::Terminal { kind: "kind".into(), value: "left\nvalue\nright".into() - }) + } ) } @@ -106,11 +205,12 @@ mod tests { }; assert_eq!( - merge(&base, &left, &right), - MergeResultNode::Conflict(CSTNode::Terminal { + merge(&base, &left, &right, &Matchings::empty(), &Matchings::empty(), + &Matchings::empty()), + CSTNode::Terminal { kind: "kind".into(), value: "<<<<<<< ours\nleft_value||||||| original\nvalue=======\nright_value>>>>>>> theirs\n".into() - }) + } ) } @@ -125,8 +225,15 @@ mod tests { value: "value_left".into(), }; assert_eq!( - merge(&base_and_right, &left, &base_and_right), - MergeResultNode::Success(left) + merge( + &base_and_right, + &left, + &base_and_right, + &Matchings::empty(), + &Matchings::empty(), + &Matchings::empty() + ), + left ) } @@ -141,8 +248,15 @@ mod tests { value: "value_right".into(), }; assert_eq!( - merge(&base_and_left, &base_and_left, &right), - MergeResultNode::Success(right), + merge( + &base_and_left, + &base_and_left, + &right, + &Matchings::empty(), + &Matchings::empty(), + &Matchings::empty() + ), + right, ) } @@ -162,6 +276,236 @@ mod tests { kind: "kind".into(), children: vec![], }, + &Matchings::empty(), + &Matchings::empty(), + &Matchings::empty(), + ); + } + + #[test] + fn merge_puts_added_nodes_in_left_only() { + let left = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "another_kind".into(), + value: "another_value".into(), + }, + CSTNode::Terminal { + kind: "kind_left".into(), + value: "value_left".into(), + }, + ], + }; + let base_and_right = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "another_kind".into(), + value: "another_value".into(), + }], + }; + + let matchings_left_base = ordered_tree_matching(&left, &base_and_right); + + assert_eq!( + CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind_left".into(), + value: "value_left".into(), + }, + CSTNode::Terminal { + kind: "another_kind".into(), + value: "another_value".into(), + } + ], + }, + merge( + &base_and_right, + &left, + &base_and_right, + &matchings_left_base, + &Matchings::empty(), + &Matchings::empty() + ) + ); + } + + #[test] + fn merge_removes_nodes_deleted_in_left_only() { + let base_and_right = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }, + CSTNode::Terminal { + kind: "deleted_in_left".into(), + value: "deleted_in_left".into(), + }, + ], + }; + let left = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }], + }; + + let matchings_left_base = ordered_tree_matching(&left, &base_and_right); + let matchings_right_base = ordered_tree_matching(&base_and_right, &base_and_right); + let matchings_left_right = ordered_tree_matching(&base_and_right, &base_and_right); + + assert_eq!( + CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }], + }, + merge( + &base_and_right, + &left, + &base_and_right, + &matchings_left_base, + &matchings_right_base, + &matchings_left_right + ) + ); + } + + #[test] + fn merge_independent_nodes_added_in_left_and_right() { + let base = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }], + }; + let left = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }, + CSTNode::Terminal { + kind: "added_in_left".into(), + value: "added_in_left".into(), + }, + ], + }; + let right = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }, + CSTNode::Terminal { + kind: "added_in_right".into(), + value: "added_in_right".into(), + }, + ], + }; + + let matchings_left_base = ordered_tree_matching(&left, &base); + let matchings_right_base = ordered_tree_matching(&right, &base); + let matchings_left_right = ordered_tree_matching(&left, &right); + + assert_eq!( + CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind".into(), + value: "value".into(), + }, + CSTNode::Terminal { + kind: "added_in_left".into(), + value: "added_in_left".into(), + }, + CSTNode::Terminal { + kind: "added_in_right".into(), + value: "added_in_right".into(), + } + ], + }, + merge( + &base, + &left, + &right, + &matchings_left_base, + &matchings_right_base, + &matchings_left_right + ) + ); + } + + #[test] + fn merge_deep_nodes_additions() { + let base = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![], + }], + }; + let left = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "added_in_left".into(), + value: "added_in_left".into(), + }], + }], + }; + let right = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "added_in_right".into(), + value: "added_in_right".into(), + }], + }], + }; + + let matchings_left_base = ordered_tree_matching(&left, &base); + let matchings_right_base = ordered_tree_matching(&right, &base); + let matchings_left_right = ordered_tree_matching(&left, &right); + + assert_eq!( + CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "added_in_left".into(), + value: "added_in_left".into(), + }, + CSTNode::Terminal { + kind: "added_in_right".into(), + value: "added_in_right".into(), + } + ] + }] + }, + merge( + &base, + &left, + &right, + &matchings_left_base, + &matchings_right_base, + &matchings_left_right + ) ); } }