Skip to main content

oak_core/source/
buffer.rs

1//! Source buffer for code generation and minification.
2
3/// A trait for types that can be converted to source code.
4pub trait ToSource {
5    /// Writes the source code representation of this type to the provided buffer.
6    fn to_source(&self, buffer: &mut SourceBuffer);
7
8    /// Converts this type to a source code string.
9    fn to_source_string(&self) -> String {
10        let mut buffer = SourceBuffer::new();
11        self.to_source(&mut buffer);
12        buffer.finish()
13    }
14}
15
16/// A buffer for building source code with intelligent spacing for minification.
17#[derive(Debug, Clone, Default)]
18pub struct SourceBuffer {
19    inner: String,
20    last_char: Option<char>,
21}
22
23impl std::fmt::Display for SourceBuffer {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.inner)
26    }
27}
28
29impl SourceBuffer {
30    /// Creates a new, empty source buffer.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Pushes a string to the buffer, automatically adding a space if necessary to prevent token merging.
36    pub fn push(&mut self, s: &str) {
37        if let Some(first) = s.chars().next() {
38            // If both the last character and the first character of the new string are "word" characters,
39            // we must insert a space to prevent them from merging into a single token (e.g., "let" + "a" -> "let a").
40            if self.is_word_char(self.last_char) && self.is_word_char(Some(first)) {
41                self.inner.push(' ')
42            }
43            self.inner.push_str(s);
44            self.last_char = s.chars().last()
45        }
46    }
47
48    /// Pushes a character to the buffer, automatically adding a space if necessary to prevent token merging.
49    pub fn push_char(&mut self, c: char) {
50        if self.is_word_char(self.last_char) && self.is_word_char(Some(c)) {
51            self.inner.push(' ')
52        }
53        self.inner.push(c);
54        self.last_char = Some(c);
55    }
56
57    /// Returns the accumulated source code as a string.
58    pub fn finish(self) -> String {
59        self.inner
60    }
61
62    /// Helper to check if a character is a "word" character (alphanumeric or underscore).
63    fn is_word_char(&self, c: Option<char>) -> bool {
64        c.map_or(false, |c| c.is_alphanumeric() || c == '_')
65    }
66}
67
68impl From<SourceBuffer> for String {
69    fn from(buffer: SourceBuffer) -> Self {
70        buffer.finish()
71    }
72}