use super::PathSegment;
use super::error::position_at;
use super::tree::Children;
use crate::error::Position;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ValueFacts {
pub single_quoted: bool,
pub multiline: bool,
pub key_spelling: Option<String>,
pub key_quoted: Option<bool>,
pub normal_layout: bool,
}
impl ValueFacts {
fn is_default(&self) -> bool {
*self == ValueFacts::default()
}
}
pub(crate) type NodeId = usize;
pub(crate) const ROOT: NodeId = 0;
#[derive(Debug, Clone, Default)]
struct Node {
facts: ValueFacts,
children: Children,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Location {
unit: usize,
value: usize,
key: Option<usize>,
}
#[derive(Debug, Clone, Default)]
struct Locations {
at: Vec<Option<Location>>,
files: Vec<(PathBuf, Vec<u8>)>,
current: usize,
}
impl Locations {
fn get(&self, node: NodeId) -> Option<Location> {
self.at.get(node).copied().flatten()
}
fn set(&mut self, node: NodeId, at: Option<Location>) {
if self.at.len() <= node {
if at.is_none() {
return;
}
self.at.resize(node + 1, None);
}
self.at[node] = at;
}
}
#[derive(Debug, Clone)]
pub struct OutputFacts {
nodes: Vec<Node>,
recorded: usize,
locations: Option<Box<Locations>>,
}
pub(crate) type ValuePath = Vec<PathSegment>;
pub(crate) type Subtree = Vec<(ValuePath, ValueFacts, Option<Location>)>;
impl Default for OutputFacts {
fn default() -> Self {
Self {
nodes: vec![Node::default()],
recorded: 0,
locations: None,
}
}
}
impl PartialEq for OutputFacts {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl Eq for OutputFacts {}
impl OutputFacts {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.recorded == 0
}
pub fn get(&self, path: &[PathSegment]) -> Option<&ValueFacts> {
self.facts_of(self.node_at(path)?)
}
pub fn insert(&mut self, path: Vec<PathSegment>, facts: ValueFacts) {
let node = if facts.is_default() {
self.node_at(&path)
} else {
Some(self.descend_or_insert(ROOT, &path))
};
if let Some(node) = node {
self.set(node, facts);
}
}
pub fn iter(&self) -> impl Iterator<Item = (Vec<PathSegment>, &ValueFacts)> {
let mut recorded = Vec::with_capacity(self.recorded);
let mut stack = vec![(ROOT, Vec::new())];
while let Some((node, path)) = stack.pop() {
let n = &self.nodes[node];
for (segment, child) in n.children.iter() {
let mut path = path.clone();
path.push(segment);
stack.push((child, path));
}
if !n.facts.is_default() {
recorded.push((path, &n.facts));
}
}
recorded.sort_by(|a, b| a.0.cmp(&b.0));
recorded.into_iter()
}
pub(crate) fn clear(&mut self) {
*self = Self::default();
}
pub(crate) fn locating() -> Self {
Self {
locations: Some(Box::default()),
..Self::default()
}
}
pub(crate) fn records_locations(&self) -> bool {
self.locations.is_some()
}
pub(crate) fn tracks_moves(&self) -> bool {
self.recorded > 0 || self.locations.is_some()
}
pub(crate) fn locate(&mut self, node: NodeId, value: usize, key: Option<usize>) {
if let Some(locations) = &mut self.locations {
let at = Location {
unit: locations.current,
value,
key,
};
locations.set(node, Some(at));
}
}
pub(crate) fn enter_unit(&mut self, file: &Path, src: &[u8]) -> usize {
let Some(locations) = &mut self.locations else {
return 0;
};
locations.files.push((file.to_path_buf(), src.to_vec()));
std::mem::replace(&mut locations.current, locations.files.len())
}
pub(crate) fn leave_unit(&mut self, unit: usize) {
if let Some(locations) = &mut self.locations {
locations.current = unit;
}
}
pub(crate) fn position_of(
&self,
path: &[PathSegment],
key: bool,
document: &[u8],
) -> Option<(Position, Option<PathBuf>)> {
let locations = self.locations.as_ref()?;
let mut node = ROOT;
let mut found = locations.get(ROOT).map(|at| (at, path.is_empty()));
for (depth, segment) in path.iter().enumerate() {
let Some(child) = self.nodes[node].children.get(segment) else {
break;
};
node = child;
if let Some(at) = locations.get(node) {
found = Some((at, depth + 1 == path.len()));
}
}
let (at, exact) = found?;
let offset = match at.key {
Some(key_at) if key && exact => key_at,
_ => at.value,
};
match at.unit {
0 => Some((position_at(document, offset), None)),
unit => {
let (file, src) = locations.files.get(unit - 1)?;
Some((position_at(src, offset), Some(file.clone())))
}
}
}
pub(crate) fn facts_of(&self, node: NodeId) -> Option<&ValueFacts> {
let facts = &self.nodes[node].facts;
(!facts.is_default()).then_some(facts)
}
pub(crate) fn child_key(&self, node: NodeId, key: &str, index: usize) -> Option<NodeId> {
self.nodes[node].children.key(key, index)
}
pub(crate) fn child_element(&self, node: NodeId, index: usize) -> Option<NodeId> {
self.nodes[node].children.element(index)
}
fn node_at(&self, path: &[PathSegment]) -> Option<NodeId> {
path.iter()
.try_fold(ROOT, |node, segment| self.nodes[node].children.get(segment))
}
pub(crate) fn child_or_insert(&mut self, node: NodeId, segment: &PathSegment) -> NodeId {
if let Some(child) = self.nodes[node].children.get(segment) {
return child;
}
let child = self.nodes.len();
self.nodes.push(Node::default());
self.nodes[node].children.insert(segment, child);
child
}
pub(crate) fn descend_or_insert(&mut self, node: NodeId, path: &[PathSegment]) -> NodeId {
path.iter()
.fold(node, |node, segment| self.child_or_insert(node, segment))
}
fn set(&mut self, node: NodeId, facts: ValueFacts) {
let was = !self.nodes[node].facts.is_default();
let now = !facts.is_default();
self.recorded = self.recorded + usize::from(now) - usize::from(was);
self.nodes[node].facts = facts;
}
pub(crate) fn update(&mut self, node: NodeId, change: impl FnOnce(&mut ValueFacts)) {
let mut facts = self.nodes[node].facts.clone();
change(&mut facts);
self.set(node, facts);
}
fn drop_subtree(&mut self, node: NodeId) {
let mut stack = vec![node];
while let Some(node) = stack.pop() {
let n = &mut self.nodes[node];
if !n.facts.is_default() {
self.recorded -= 1;
}
stack.extend(n.children.take_all());
}
}
pub(crate) fn clear_below(&mut self, node: NodeId) {
for child in self.nodes[node].children.take_all() {
self.drop_subtree(child);
}
}
pub(crate) fn replaced(&mut self, object: NodeId, key: &str, slot: Option<usize>) {
let children = &mut self.nodes[object].children;
let dropped: Vec<NodeId> = match slot {
Some(slot) => children.take_value(key, slot).into_iter().collect(),
None => children
.take_values(key)
.into_iter()
.map(|(_, c)| c)
.collect(),
};
for child in dropped {
self.drop_subtree(child);
}
}
pub(crate) fn collected(&mut self, object: NodeId, key: &str, count: usize) {
let moved: Vec<(usize, NodeId)> = (0..count)
.filter_map(|index| {
let child = self.nodes[object].children.take_value(key, index)?;
Some((index, child))
})
.collect();
if moved.is_empty() {
return;
}
let array = self.nodes.len();
self.nodes.push(Node::default());
if let Some(locations) = &mut self.locations {
let at = locations.get(moved[0].1);
locations.set(array, at);
}
let first = PathSegment::Key {
key: key.to_owned(),
index: 0,
};
self.nodes[object].children.insert(&first, array);
for (index, child) in moved {
let element = PathSegment::Index(index);
self.nodes[array].children.insert(&element, child);
}
}
pub(crate) fn subtree(&self, from: &[PathSegment]) -> Subtree {
let Some(node) = self.node_at(from) else {
return Vec::new();
};
let mut facts = Vec::new();
let mut stack = vec![(node, Vec::new())];
while let Some((node, path)) = stack.pop() {
let n = &self.nodes[node];
let at = self.locations.as_ref().and_then(|l| l.get(node));
if !n.facts.is_default() || at.is_some() {
facts.push((path.clone(), n.facts.clone(), at));
}
for (segment, child) in n.children.iter() {
let mut path = path.clone();
path.push(segment);
stack.push((child, path));
}
}
facts
}
pub(crate) fn graft(&mut self, to: NodeId, facts: Subtree) {
for (rest, f, at) in facts {
let node = self.descend_or_insert(to, &rest);
self.set(node, f);
if let (Some(locations), Some(_)) = (&mut self.locations, at) {
locations.set(node, at);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn key(k: &str, index: usize) -> PathSegment {
PathSegment::Key {
key: k.into(),
index,
}
}
fn sq() -> ValueFacts {
ValueFacts {
single_quoted: true,
..ValueFacts::default()
}
}
#[test]
fn default_facts_are_not_stored() {
let mut facts = OutputFacts::new();
facts.insert(vec![key("a", 0)], ValueFacts::default());
assert!(facts.is_empty());
let a = facts.child_or_insert(ROOT, &key("a", 0));
assert!(facts.is_empty());
facts.update(a, |f| f.single_quoted = true);
facts.update(a, |f| f.key_quoted = Some(true));
assert_eq!(facts.get(&[key("a", 0)]).unwrap().key_quoted, Some(true));
facts.update(a, |f| f.single_quoted = false);
facts.update(a, |f| f.key_quoted = None);
assert!(facts.is_empty());
assert_eq!(facts, OutputFacts::new());
}
#[test]
fn locations() {
let document = b"a = 1\nb {\n c = x\n}";
let mut facts = OutputFacts::locating();
assert!(facts.tracks_moves() && facts.records_locations());
facts.locate(ROOT, 0, None);
let b = facts.child_or_insert(ROOT, &key("b", 0));
facts.locate(b, 8, Some(6));
let c = facts.child_or_insert(b, &key("c", 0));
facts.locate(c, 16, Some(12));
fn line_col(facts: &OutputFacts, path: &[PathSegment], on_key: bool) -> (usize, usize) {
let (p, file) = facts
.position_of(path, on_key, b"a = 1\nb {\n c = x\n}")
.unwrap();
assert_eq!(file, None);
(p.line, p.column)
}
assert_eq!(line_col(&facts, &[], false), (1, 1));
assert_eq!(line_col(&facts, &[key("b", 0), key("c", 0)], false), (3, 7));
assert_eq!(line_col(&facts, &[key("b", 0), key("c", 0)], true), (3, 3));
assert_eq!(line_col(&facts, &[key("b", 0), key("d", 0)], true), (2, 3));
assert_eq!(line_col(&facts, &[key("z", 0)], false), (1, 1));
facts.collected(b, "c", 1);
assert_eq!(line_col(&facts, &[key("b", 0), key("c", 0)], false), (3, 7));
let copied = facts.subtree(&[key("b", 0)]);
let e = facts.child_or_insert(ROOT, &key("e", 0));
facts.graft(e, copied);
let inner = [key("e", 0), key("c", 0), PathSegment::Index(0)];
assert_eq!(line_col(&facts, &inner, false), (3, 7));
let before = facts.enter_unit(Path::new("/i.conf"), b"\n k = v");
let k = facts.child_or_insert(ROOT, &key("k", 0));
facts.locate(k, 7, Some(3));
facts.leave_unit(before);
let (p, file) = facts.position_of(&[key("k", 0)], false, document).unwrap();
assert_eq!(
((p.line, p.column), file),
((2, 7), Some(PathBuf::from("/i.conf")))
);
let mut plain = OutputFacts::new();
assert!(!plain.tracks_moves());
plain.locate(ROOT, 3, None);
assert_eq!(plain.position_of(&[], false, document), None);
}
#[test]
fn replaced_collected_and_grafted_paths() {
let mut facts = OutputFacts::new();
facts.insert(vec![key("o", 0), key("a", 0)], sq());
facts.insert(vec![key("o", 0), key("a", 1), PathSegment::Index(0)], sq());
facts.insert(vec![key("o", 0), key("b", 0)], sq());
let o = facts.child_key(ROOT, "o", 0).unwrap();
facts.collected(o, "a", 1);
assert!(
facts
.get(&[key("o", 0), key("a", 0), PathSegment::Index(0)])
.is_some()
);
facts.replaced(o, "a", Some(1));
assert_eq!(facts.iter().count(), 2);
let copied = facts.subtree(&[key("o", 0)]);
let p = facts.child_or_insert(ROOT, &key("p", 0));
facts.graft(p, copied);
assert!(facts.get(&[key("p", 0), key("b", 0)]).is_some());
facts.clear_below(o);
assert_eq!(facts.iter().count(), 2);
let paths: Vec<_> = facts.iter().map(|(path, _)| path).collect();
assert_eq!(
paths,
[
vec![key("p", 0), key("a", 0), PathSegment::Index(0)],
vec![key("p", 0), key("b", 0)],
]
);
facts.replaced(p, "a", None);
facts.replaced(p, "b", None);
assert!(facts.is_empty());
}
}