diff --git a/bin/src/main.rs b/bin/src/main.rs index 3077282..d7d3231 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -1,34 +1,3 @@ -use matching::ordered_tree_matching; -use merge::merge; -use model::CSTNode; -use parsing::ParserConfiguration; - -// fn run_semi_structured_merge_on_revisions<'a>( -// language: model::Language, -// base: &'a str, -// left: &'a str, -// right: &'a str, -// ) -> Result, &'static str> { -// let parser_configuration = ParserConfiguration::from_language(language); - -// let base_tree = parsing::parse_string(base, &parser_configuration)?; -// let left_tree = parsing::parse_string(left, &parser_configuration)?; -// let right_tree = parsing::parse_string(right, &parser_configuration)?; - -// let matchings_left_base = ordered_tree_matching(&left_tree, &base_tree); -// let matchings_right_base = ordered_tree_matching(&right_tree, &base_tree); -// let matchings_left_right = ordered_tree_matching(&left_tree, &right_tree); - -// Ok(merge( -// &base_tree.clone(), -// &left_tree.clone(), -// &right_tree.clone(), -// &matchings_left_base.clone(), -// &matchings_right_base.clone(), -// &matchings_left_right.clone(), -// )) -// } - fn main() { let base = r#" public static interface HelloWorld { @@ -38,115 +7,32 @@ fn main() { let left = r#" public static interface HelloWorld { void sayHello(String name); - void sayBye(String name); } "#; let right = r#" public static interface HelloWorld { - void killAllHumans(); void sayHello(String name); } "#; - // let result = run_semi_structured_merge_on_revisions(model::Language::Java, base, left, right); - - // println!("{:#?}", pretty_print(result.unwrap())) -} - -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 - }) - } - CSTNode::Conflict { .. } => "Conflict found".into(), - } -} - -// #[cfg(test)] -// mod tests { -// use model::language::Language; -// use model::CSTNode; -// use parsing::ParserConfiguration; - -// use crate::run_semi_structured_merge_on_revisions; + let parser_configuration = parsing::ParserConfiguration::from_language(model::Language::Java); -// fn parse_java_string(contents: &str) -> CSTNode { -// parsing::parse_string(contents, ParserConfiguration::from_language(model::Language::Java)).unwrap() -// } + let base_tree = parsing::parse_string(base, &parser_configuration).unwrap(); + let left_tree = parsing::parse_string(left, &parser_configuration).unwrap(); + let right_tree = parsing::parse_string(right, &parser_configuration).unwrap(); -// #[test] -// fn it_merges_three_java_revisions_that_are_equal() { -// let code = r#" -// public static interface HelloWorld { -// void sayHello(String name); -// void sayBye(String name); -// } -// "#; + let matchings_left_base = matching::ordered_tree_matching(&left_tree, &base_tree); + let matchings_right_base = matching::ordered_tree_matching(&right_tree, &base_tree); + let matchings_left_right = matching::ordered_tree_matching(&left_tree, &right_tree); -// let result = -// run_semi_structured_merge_on_revisions(model::Language::Java, code, code, code); + let result = merge::merge( + &base_tree, + &left_tree, + &right_tree, + &matchings_left_base, + &matchings_right_base, + &matchings_left_right, + ); -// assert_eq!(parse_java_string(code), result.unwrap()) -// } - -// #[test] -// fn it_merges_three_java_revisions_that_adds_the_same_node() { -// let base = r#" -// public static interface HelloWorld { -// void sayBye(String name); -// } -// "#; - -// let parents = r#" -// public static interface HelloWorld { -// void sayHello(String name); -// void sayBye(String name); -// } -// "#; - -// let merge = r#" -// public static interface HelloWorld { -// void sayHello(String name); -// void sayBye(String name); -// } -// "#; - -// let result = -// run_semi_structured_merge_on_revisions(model::Language::Java, base, parents, parents); - -// assert_eq!(parse_java_string(merge), result.unwrap()) -// } - -// #[test] -// fn it_merges_three_java_revisions_that_removes_the_same_node() { -// let base = r#" -// public static interface HelloWorld { -// void sayHello(String name); -// void sayBye(String name); -// } -// "#; - -// let parents = r#" -// public static interface HelloWorld { -// void sayBye(String name); -// } -// "#; - -// let merge = r#" -// public static interface HelloWorld { -// void sayBye(String name); -// } -// "#; - -// let result = -// run_semi_structured_merge_on_revisions(model::Language::Java, base, parents, parents); - -// assert_eq!(parse_java_string(merge), result.unwrap()) -// } -// } + println!("{:#?}", result) +} diff --git a/merge/src/odered_merge.rs b/merge/src/odered_merge.rs index c8c511f..4733c06 100644 --- a/merge/src/odered_merge.rs +++ b/merge/src/odered_merge.rs @@ -126,7 +126,10 @@ pub fn ordered_merge<'a>( } cur_right = children_right_it.next(); } - (None, Some(_), None, None, None) => {} + (None, Some(_), None, None, None) => { + result_children.push(cur_right.unwrap().to_owned()); + cur_right = children_right_it.next(); + } (None, None, Some(matching_base_left), Some(_), Some(_)) => { if !matching_base_left.is_perfect_match { result_children.push(CSTNode::Conflict { @@ -143,7 +146,32 @@ pub fn ordered_merge<'a>( } cur_left = children_left_it.next(); } - (None, None, Some(_), None, Some(_)) => {} + (None, None, Some(matching_base_left), None, Some(matching_base_right)) => { + match (matching_base_left.is_perfect_match, matching_base_right.is_perfect_match) { + (true, true) => {} + (true, false) => { + result_children.push(CSTNode::Conflict { + left: Some(cur_left.unwrap().to_owned()).into(), + right: None.into() + }) + } + (false, true) => { + result_children.push(CSTNode::Conflict { + left: None.into(), + right: Some(cur_right.unwrap().to_owned()).into() + }) + } + (false, false) => { + result_children.push(CSTNode::Conflict { + left: Some(cur_left.unwrap().to_owned()).into(), + right: Some(cur_right.unwrap().to_owned()).into() + }) + } + }; + + cur_left = children_left_it.next(); + cur_right = children_right_it.next(); + } (None, None, Some(matching_base_left), None, None) => { result_children.push(cur_right.unwrap().to_owned()); @@ -162,7 +190,10 @@ pub fn ordered_merge<'a>( cur_left = children_left_it.next(); } - (None, None, None, Some(_), None) => {} + (None, None, None, Some(_), None) => { + result_children.push(cur_left.unwrap().to_owned()); + cur_left = children_left_it.next(); + } (None, None, None, None, Some(matching_base_right)) => { result_children.push(cur_left.unwrap().to_owned()); @@ -1074,4 +1105,185 @@ mod tests { }, ); } + + #[test] + fn it_merges_when_a_parent_adds_one_node() { + let base = CSTNode::NonTerminal { + kind: "kind", + children: vec![], + }; + + let parent_a = CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::Terminal { + kind: "kind_a", + value: "value_a".into(), + }], + }; + + let parent_b = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![ + CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }, + CSTNode::Terminal { + kind: "kind_a", + value: "value_a".into(), + }, + ], + }; + + let expected_merge = CSTNode::NonTerminal { + kind: "kind", + children: vec![ + CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }, + CSTNode::Terminal { + kind: "kind_a", + value: "value_a".into(), + }, + ], + }; + + assert_merge_is_correct_and_idempotent_with_respect_to_parent_side( + base, + parent_a, + parent_b, + expected_merge, + ) + } + + #[test] + fn it_does_not_detect_a_conflict_if_am_merging_two_subtrees_that_have_not_changed_mutually() { + let base = CSTNode::NonTerminal { + kind: "kind", + children: vec![ + CSTNode::Terminal { + kind: "kind_b", + value: "value_b".into(), + }, + CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }, + ], + }; + + let parent_a = CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::Terminal { + kind: "kind_b", + value: "value_b".into(), + }], + }; + + let parent_b = CSTNode::NonTerminal { + kind: "kind".into(), + children: vec![CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }], + }; + + let expected_merge = CSTNode::NonTerminal { + kind: "kind", + children: vec![], + }; + + assert_merge_is_correct_and_idempotent_with_respect_to_parent_side( + base, + parent_a, + parent_b, + expected_merge, + ); + } + + #[test] + fn it_detects_a_conflict_if_am_merging_two_subtrees_that_delete_a_node_that_was_changed_in_another_parent( + ) { + let base = CSTNode::NonTerminal { + kind: "kind", + children: vec![ + CSTNode::NonTerminal { + kind: "subtree_a", + children: vec![CSTNode::Terminal { + kind: "kind_b", + value: "value_b".into(), + }], + }, + CSTNode::NonTerminal { + kind: "subtree_b", + children: vec![CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }], + }, + ], + }; + + let parent_a = CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::NonTerminal { + kind: "subtree_b", + children: vec![CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }], + }], + }; + + let parent_b = CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::NonTerminal { + kind: "subtree_a", + children: vec![CSTNode::Terminal { + kind: "kind_a", + value: "value_c".into(), + }], + }], + }; + + assert_merge_output_is( + &base, + &parent_a, + &parent_b, + &CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::Conflict { + left: Some(CSTNode::NonTerminal { + kind: "subtree_b", + children: vec![CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }], + }) + .into(), + right: None.into(), + }], + }, + ); + assert_merge_output_is( + &base, + &parent_b, + &parent_a, + &CSTNode::NonTerminal { + kind: "kind", + children: vec![CSTNode::Conflict { + left: None.into(), + right: Some(CSTNode::NonTerminal { + kind: "subtree_b", + children: vec![CSTNode::Terminal { + kind: "kind_c", + value: "value_c".into(), + }], + }) + .into(), + }], + }, + ); + } } diff --git a/model/src/cst_node.rs b/model/src/cst_node.rs index 9dce135..3cd1bbe 100644 --- a/model/src/cst_node.rs +++ b/model/src/cst_node.rs @@ -13,3 +13,20 @@ pub enum CSTNode<'a> { right: Box>>, }, } + +impl ToString for CSTNode<'_> { + fn to_string(&self) -> String { + match self { + CSTNode::Terminal { value, .. } => value.to_string(), + CSTNode::NonTerminal { children, .. } => { + children.iter().fold(String::new(), |acc, current| { + let mut result = acc.to_owned(); + result.push_str(" "); + result.push_str(¤t.clone().to_string()); + result + }) + } + CSTNode::Conflict { .. } => "Conflict found".into(), + } + } +}