1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Context stores debug information in a stack, and
//! prints it in a clear format

#[derive(Clone, Debug)]
pub struct Context {
    pub description: String,
    nodes: Vec<Node>,
}

impl Context {
    pub fn new() -> Self {
        Self {
            description: "context description not set".to_string(),
            nodes: vec![],
        }
    }

    pub fn with_description(description: &str) -> Self {
      Self {
        description: description.to_string(),
        nodes: vec![],
      }
    }

    pub fn is_empty(&self) -> bool {
        self.nodes.len() == 0
    }

    pub fn get_length(&self) -> i32 {
        self.nodes.len() as i32
    }

    pub fn push(&mut self, node_item: &str, node_type: &str, node_info: &str) {
        self.nodes.push(Node {
            node_item: node_item.to_string(),
            node_type: node_type.to_string(),
            node_info: node_info.to_string(),
        });
    }

    pub fn pop(&mut self) -> String {
        if self.is_empty() {
            panic!("Error: tried to pop an item from an empty Context stack");
        }
        let node = self.nodes.pop().unwrap();
        let info = if node.node_info.eq(&String::from("")) {
            String::from("")
        } else {
            format!(" >> {}", node.node_info)
        };

        format!("{}: {}{}", node.node_item, node.node_type, info)
    }

    pub fn context_to_string(&self) -> String {
        self.print_with_tabs(0, 2)
    }

    pub fn print_with_context(&self, message: &str) -> String {
        [message, "\n", &self.print_with_tabs(1, 0)].concat()
    }

    fn print_with_tabs(&self, tabs: i32, size: i32) -> String {
        let width = (tabs + 1) * size;
        let pad_start = format!("{:width$}", " ", width = width as usize);
        let pad_end = format!("\n{:width$}", " ", width = (width + 1) as usize);

        let mut result = String::new();
        result.push_str(&pad_start);

        let ctx = ["Context: ", &self.description].concat();
        result.push_str(&ctx);

        if self.is_empty() {
            result.push_str(&pad_end);
            result.push_str("context stack is empty");
            return result;
        }

        for i in (0..self.get_length() as usize).rev() {
            let node = &self.nodes[i];
            result.push_str(&pad_end);
            let msg = format!(
                "at {} : {} >> {}",
                node.node_item, node.node_type, node.node_info
            );
            result.push_str(&msg);
        }
        result
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
struct Node {
    node_item: String,
    node_type: String,
    node_info: String,
}

impl std::fmt::Display for Context {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "({}, {:?})", self.description, self.nodes)
    }
}