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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::fmt;

use koto_memory::Address;

use crate::{KString, Vm};

/// A helper for converting Koto values to strings
#[derive(Default)]
pub struct DisplayContext<'a> {
    result: String,
    vm: Option<&'a Vm>,
    // A contained value might need to be displayed differently,
    // - Strings should be displayed with quotes when they're inside a container.
    // - Containers should check the parent list to avoid recursive display operations.
    parent_containers: Vec<Address>,
}

impl<'a> DisplayContext<'a> {
    /// Makes a display context with the given VM
    pub fn with_vm(vm: &'a Vm) -> Self {
        Self {
            result: String::default(),
            vm: Some(vm),
            parent_containers: Vec::default(),
        }
    }

    /// Makes a display context with the given VM and reserved capacity
    pub fn with_vm_and_capacity(vm: &'a Vm, capacity: usize) -> Self {
        Self {
            result: String::with_capacity(capacity),
            vm: Some(vm),
            parent_containers: Vec::default(),
        }
    }

    /// Appends to the end of the string
    pub fn append<'b>(&mut self, s: impl Into<StringBuilderAppend<'b>>) {
        s.into().append(&mut self.result);
    }

    /// Returns the resulting string and consumes the context
    pub fn result(self) -> String {
        self.result
    }

    /// Returns a reference to the context's VM
    pub fn vm(&self) -> &Option<&'a Vm> {
        &self.vm
    }

    /// Returns true if the value that's being displayed is in a container
    pub fn is_contained(&self) -> bool {
        !self.parent_containers.is_empty()
    }

    /// Returns true if the given ID is present in the parent container list
    pub fn is_in_parents(&self, id: Address) -> bool {
        self.parent_containers
            .iter()
            .any(|parent_id| *parent_id == id)
    }

    /// Adds the given ID to the parents list
    ///
    /// Containers should call this before displaying their contained values.
    pub fn push_container(&mut self, id: Address) {
        self.parent_containers.push(id);
    }

    /// Pops the previously added parent ID
    ///
    /// Containers should call this after displaying their contained values.
    pub fn pop_container(&mut self) {
        self.parent_containers.pop();
    }
}

impl<'a> fmt::Write for DisplayContext<'a> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.append(s);
        Ok(())
    }
}

/// Types that can be appended to [DisplayContext]
pub enum StringBuilderAppend<'a> {
    Char(char),
    Str(&'a str),
    String(String),
    KString(KString),
    KStringRef(&'a KString),
}

impl From<char> for StringBuilderAppend<'_> {
    fn from(value: char) -> Self {
        StringBuilderAppend::Char(value)
    }
}

impl<'a> From<&'a str> for StringBuilderAppend<'a> {
    fn from(value: &'a str) -> Self {
        StringBuilderAppend::Str(value)
    }
}

impl From<String> for StringBuilderAppend<'_> {
    fn from(value: String) -> Self {
        StringBuilderAppend::String(value)
    }
}

impl From<KString> for StringBuilderAppend<'_> {
    fn from(value: KString) -> Self {
        StringBuilderAppend::KString(value)
    }
}

impl<'a> From<&'a KString> for StringBuilderAppend<'a> {
    fn from(value: &'a KString) -> Self {
        StringBuilderAppend::KStringRef(value)
    }
}

impl<'a> StringBuilderAppend<'a> {
    fn append(self, string: &mut String) {
        match self {
            StringBuilderAppend::Char(c) => string.push(c),
            StringBuilderAppend::Str(s) => string.push_str(s),
            StringBuilderAppend::String(s) => string.push_str(&s),
            StringBuilderAppend::KString(s) => string.push_str(&s),
            StringBuilderAppend::KStringRef(s) => string.push_str(s),
        }
    }
}