cco 0.2.0

cascading configuration
Documentation
//! [Stack] and [Request] are used to track evaluation state in [Context]

use crate::eval::index::Index;

#[derive(Debug)]
pub struct Stack {
    requests: Vec<Request>,
}

impl Stack {
    pub fn new(target: Index) -> Self {
        Self {
            requests: vec![Request::new(vec![target])],
        }
    }

    pub fn request(&mut self, targets: impl IntoIterator<Item = Index>) {
        self.requests
            .push(Request::new(targets.into_iter().collect()));
    }

    /// Tries to skip the current target
    pub fn try_skip_current(&mut self) -> bool {
        let Some(request) = self.requests.last_mut() else {
            return false;
        };

        if request.skip() {
            return true;
        }

        self.requests.pop();
        self.try_skip_current()
    }

    /// Mark the current request as completed
    pub fn remove(&mut self, index: Index) {
        self.requests.retain_mut(|req| {
            req.complete(index);
            req.current().is_some()
        });
    }

    pub fn stack(&self) -> Vec<Index> {
        self.requests.iter().map(|r| r.current().unwrap()).collect()
    }

    pub fn get_current(&self) -> Option<Index> {
        self.requests.last().and_then(|r| r.current())
    }

    pub fn len(&self) -> usize {
        self.requests.len()
    }
}

#[derive(Debug, Clone)]
struct Request {
    /// Pending expressions
    pending: Vec<Index>,

    /// Failed expressions
    failed: Vec<Index>,
}

impl Request {
    /// Create a new Request from a list of targets
    pub fn new(targets: Vec<Index>) -> Self {
        Self {
            pending: targets,
            failed: vec![],
        }
    }

    /// The target currently in the queue
    pub fn current(&self) -> Option<Index> {
        self.pending.last().copied()
    }

    /// Try to skip one target if more pending exist
    pub fn skip(&mut self) -> bool {
        let Some(failed) = self.pending.pop() else {
            return false;
        };

        self.failed.push(failed);
        !self.pending.is_empty()
    }

    /// Mark target as completed and reset all failed items
    pub fn complete(&mut self, target: Index) {
        self.pending.append(&mut self.failed);
        self.pending.retain(|t| *t != target);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn request_new_empty() {
        let empty_request = Request::new(vec![]);
        assert_eq!(empty_request.current(), None);
    }

    #[test]
    fn request_new() {
        let empty_request = Request::new(vec![Index::new(1)]);
        assert_eq!(empty_request.current(), Some(Index::new(1)));
    }

    #[test]
    fn request_skip() {
        {
            let mut request = Request::new(vec![]);
            assert!(!request.skip(), "can not skip empty requests");
        }

        {
            let mut request = Request::new(vec![Index::new(1)]);
            assert!(!request.skip(), "can not skip when only one pending");
        }

        {
            let mut request = Request::new(vec![Index::new(1), Index::new(2)]);
            let skipped = request.current().unwrap();
            assert!(request.skip(), "can skip when 2 or more pending");
            let current = request.current().unwrap();
            assert_ne!(skipped, current, "skip skips current item");

            {
                let mut request_complete_current = request.clone();
                request_complete_current.complete(current);
                assert_eq!(
                    request_complete_current.current(),
                    Some(skipped),
                    "current completed, skipped is pending"
                );
            }

            {
                let mut request_complete_skipped = request.clone();
                request_complete_skipped.complete(skipped);
                assert_eq!(
                    request_complete_skipped.current(),
                    Some(current),
                    "skipped  completed, current is pending"
                );
            }
        }
    }
}