logix_type/
loader.rs

1use std::{fmt, io::Read, path::Path, sync::Arc};
2
3use bstr::ByteSlice;
4use indexmap::IndexMap;
5use logix_vfs::LogixVfs;
6
7use crate::{error::ParseError, parser::LogixParser, token::Token, type_trait::LogixType};
8
9#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10struct InnerCachedFile {
11    path: Arc<Path>,
12    data: Arc<[u8]>,
13}
14
15#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub(crate) struct CachedFile {
17    inner: Box<InnerCachedFile>,
18}
19
20impl CachedFile {
21    pub(crate) fn empty() -> CachedFile {
22        Self {
23            inner: Box::new(InnerCachedFile {
24                path: Arc::from(Path::new("")),
25                data: Arc::from(b"".as_slice()),
26            }),
27        }
28    }
29
30    #[cfg(test)]
31    pub(crate) fn from_slice(path: impl AsRef<Path>, data: &[u8]) -> CachedFile {
32        Self {
33            inner: Box::new(InnerCachedFile {
34                path: Arc::from(Path::new(path.as_ref())),
35                data: Arc::from(data),
36            }),
37        }
38    }
39
40    pub fn path(&self) -> &Path {
41        &self.inner.path
42    }
43
44    pub fn lines(&self) -> bstr::Lines {
45        self.inner.data.lines()
46    }
47
48    pub fn data(&self) -> &[u8] {
49        &self.inner.data
50    }
51}
52
53impl fmt::Debug for CachedFile {
54    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55        f.debug_tuple("CachedFile").field(&self.inner.path).finish()
56    }
57}
58
59/// Load a logix configuration
60#[derive(Debug)]
61pub struct LogixLoader<FS: LogixVfs> {
62    fs: FS,
63    files: IndexMap<Arc<Path>, Arc<[u8]>>,
64    tmp: Vec<u8>,
65}
66
67impl<FS: LogixVfs> LogixLoader<FS> {
68    /// Returns a new loader with the files provided by `fs`
69    pub fn new(fs: FS) -> Self {
70        Self {
71            fs,
72            files: IndexMap::new(),
73            tmp: Vec::with_capacity(0x10000),
74        }
75    }
76
77    pub(crate) fn get_file(&self, path: impl AsRef<Path>) -> Option<CachedFile> {
78        let (key, value) = self.files.get_key_value(path.as_ref())?;
79
80        Some(CachedFile {
81            inner: Box::new(InnerCachedFile {
82                path: key.clone(),
83                data: value.clone(),
84            }),
85        })
86    }
87
88    pub(crate) fn open_file(
89        &mut self,
90        path: impl AsRef<Path>,
91    ) -> Result<CachedFile, logix_vfs::Error> {
92        match self
93            .files
94            .entry(Arc::<Path>::from(self.fs.canonicalize_path(path.as_ref())?))
95        {
96            indexmap::map::Entry::Vacant(entry) => {
97                let path = entry.key().clone();
98                self.tmp.clear();
99                let mut r = self.fs.open_file(entry.key())?;
100                r.read_to_end(&mut self.tmp)
101                    .map_err(|e| logix_vfs::Error::from_io(entry.key().to_path_buf(), e))?;
102                let data = entry.insert(Arc::from(self.tmp.as_slice())).clone();
103                Ok(CachedFile {
104                    inner: Box::new(InnerCachedFile { path, data }),
105                })
106            }
107            indexmap::map::Entry::Occupied(entry) => Ok(CachedFile {
108                inner: Box::new(InnerCachedFile {
109                    path: entry.key().clone(),
110                    data: entry.get().clone(),
111                }),
112            }),
113        }
114    }
115
116    /// Load the file located at `path` inside `FS` and parse it as `T`
117    pub fn load_file<T: LogixType>(&mut self, path: impl AsRef<Path>) -> Result<T, ParseError> {
118        let file = self.open_file(path)?;
119        let mut p = LogixParser::new(self, &file);
120
121        let ret = T::logix_parse(&mut p)?;
122
123        // This will either skip any newlines and comments, or return EOF
124        p.req_newline(T::descriptor().name)?;
125
126        // From now on EOF should always be returned
127        p.req_token(T::descriptor().name, Token::Newline(true))?;
128
129        Ok(ret.value)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use logix_vfs::RelFs;
136
137    use super::*;
138
139    #[test]
140    fn basics() {
141        let tmp = tempfile::tempdir().unwrap();
142        std::fs::write(tmp.path().join("test.logix"), b"10").unwrap();
143
144        format!(
145            "{:?}",
146            CachedFile {
147                inner: Box::new(InnerCachedFile {
148                    path: Arc::from(Path::new("a")),
149                    data: Arc::from(b"".as_slice()),
150                })
151            }
152        );
153
154        let mut loader = LogixLoader::new(RelFs::new(tmp.path()));
155        loader.load_file::<u32>("test.logix").unwrap();
156        loader.load_file::<u32>("test.logix").unwrap(); // Twice to test cache
157    }
158}