Compare Node by content and position

pull/25/head
Wilfred Hughes 2021-07-03 12:05:04 +07:00
parent ad76bb9db9
commit c5511fdd9b
2 changed files with 76 additions and 28 deletions

@ -1,11 +1,8 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::{BinaryHeap, HashMap}; use std::collections::{BinaryHeap, HashMap};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use crate::positions::SingleLineSpan;
use crate::tree_diff::{ChangeKind, Node}; use crate::tree_diff::{ChangeKind, Node};
use typed_arena::Arena; use typed_arena::Arena;
use Edge::*; use Edge::*;
@ -16,33 +13,24 @@ struct Vertex<'a> {
rhs_node: Option<&'a Node<'a>>, rhs_node: Option<&'a Node<'a>>,
} }
fn node_pos<'a>(node: &'a Node<'a>) -> (Option<Vec<SingleLineSpan>>, Option<Vec<SingleLineSpan>>) { // When walking the graph, we want to consider nodes distinct if they
// TODO: get first SingleLineSpan rather than cloning the whole vec. // have different content or if they have different positions. There
match node { // may be multiple nodes with same content at different positions.
Node::List {
open_position,
close_position,
..
} => (Some(open_position.clone()), Some(close_position.clone())),
Node::Atom { position, .. } => (Some(position.clone()), None),
}
}
// Compare nodes by position. If we have multiple atoms with the same
// content, we don't want to think that we've found a route to all of
// them.
impl<'a> PartialEq for Vertex<'a> { impl<'a> PartialEq for Vertex<'a> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.lhs_node.map(node_pos) == other.lhs_node.map(node_pos) match (self.lhs_node, other.lhs_node) {
&& self.rhs_node.map(node_pos) == other.rhs_node.map(node_pos) (Some(lhs_node), Some(rhs_node)) => lhs_node.equal_content_and_pos(rhs_node),
(None, None) => true,
_ => false,
}
} }
} }
impl<'a> Eq for Vertex<'a> {} impl<'a> Eq for Vertex<'a> {}
impl<'a> Hash for Vertex<'a> { impl<'a> Hash for Vertex<'a> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.lhs_node.map(node_pos).hash(state); self.lhs_node.hash(state);
self.rhs_node.map(node_pos).hash(state); self.rhs_node.hash(state);
} }
} }

@ -250,6 +250,61 @@ impl<'a> Node<'a> {
_ => false, _ => false,
} }
} }
/// Does this `Node` have the same position in all its subnodes?
///
/// Nodes with different numbers of children return false
/// regardless of top-level positions.
fn equal_pos(&self, other: &Self) -> bool {
match (&self, other) {
(
Atom {
position: lhs_position,
..
},
Atom {
position: rhs_position,
..
},
) => lhs_position == rhs_position,
(
List {
open_position: lhs_open_position,
close_position: lhs_close_position,
children: lhs_children,
..
},
List {
open_position: rhs_open_position,
close_position: rhs_close_position,
children: rhs_children,
..
},
) => {
if lhs_open_position != rhs_open_position
|| lhs_close_position != rhs_close_position
{
return false;
}
if lhs_children.len() != rhs_children.len() {
return false;
}
for (lhs_child, rhs_child) in lhs_children.iter().zip(rhs_children.iter()) {
if !lhs_child.equal_pos(rhs_child) {
return false;
}
}
true
}
_ => false,
}
}
pub fn equal_content_and_pos(&self, other: &Self) -> bool {
self.equal_pos(other) && self.equal_content(other)
}
} }
pub fn set_next<'a>(node: &'a Node<'a>) { pub fn set_next<'a>(node: &'a Node<'a>) {
@ -276,7 +331,7 @@ fn set_next_<'a>(node: &'a Node<'a>, new_next: Option<&'a Node<'a>>) {
impl<'a> PartialEq for Node<'a> { impl<'a> PartialEq for Node<'a> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.equal_content(other) self.equal_content_and_pos(other)
} }
} }
impl<'a> Eq for Node<'a> {} impl<'a> Eq for Node<'a> {}
@ -285,18 +340,23 @@ impl<'a> Hash for Node<'a> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
match self { match self {
List { List {
open_position,
open_delimiter, open_delimiter,
close_position,
close_delimiter, close_delimiter,
children, children,
.. ..
} => { } => {
open_position.hash(state);
open_delimiter.hash(state); open_delimiter.hash(state);
close_delimiter.hash(state); close_delimiter.hash(state);
close_position.hash(state);
for child in children { for child in children {
child.hash(state); child.hash(state);
} }
} }
Atom { content, .. } => { Atom { position, content, .. } => {
position.hash(state);
content.hash(state); content.hash(state);
} }
} }
@ -475,7 +535,7 @@ mod tests {
} }
#[test] #[test]
fn test_atom_equality_ignores_change_and_pos() { fn test_atom_equality_ignores_change() {
assert_eq!( assert_eq!(
Atom { Atom {
next: Cell::new(None), next: Cell::new(None),
@ -492,9 +552,9 @@ mod tests {
next: Cell::new(None), next: Cell::new(None),
change: Cell::new(None), change: Cell::new(None),
position: vec![SingleLineSpan { position: vec![SingleLineSpan {
line: 10.into(), line: 1.into(),
start_col: 20, start_col: 2,
end_col: 30 end_col: 3
}], }],
content: "foo".into(), content: "foo".into(),
kind: Other, kind: Other,