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#[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 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 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 p.req_newline(T::descriptor().name)?;
125
126 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(); }
158}