molframe_core/io/input/
source.rs1use super::InputBuffer;
4use crate::{Code, Diagnostic, ExecutionContext, MemoryReservation};
5use std::fs::File;
6use std::io::{ErrorKind, Read, Seek, SeekFrom};
7use std::path::Path;
8
9#[derive(Clone, Copy, Debug)]
11pub struct ByteWindow<'a> {
12 start: u64,
13 bytes: &'a [u8],
14}
15
16impl<'a> ByteWindow<'a> {
17 #[must_use]
19 pub const fn new(start: u64, bytes: &'a [u8]) -> Self {
20 Self { start, bytes }
21 }
22
23 #[must_use]
25 pub const fn start(self) -> u64 {
26 self.start
27 }
28
29 #[must_use]
31 pub const fn bytes(self) -> &'a [u8] {
32 self.bytes
33 }
34
35 pub fn end(self) -> Result<u64, Diagnostic> {
41 let length =
42 u64::try_from(self.bytes.len()).map_err(|_| unrepresentable(self.bytes.len()))?;
43 self.start
44 .checked_add(length)
45 .ok_or_else(|| unrepresentable("an offset greater than u64::MAX"))
46 }
47}
48
49pub trait SourceBytes {
51 fn window(&mut self, start: u64, len: usize) -> Result<ByteWindow<'_>, Diagnostic>;
57
58 fn len_hint(&self) -> Option<u64>;
60}
61
62impl SourceBytes for InputBuffer {
63 fn window(&mut self, start: u64, len: usize) -> Result<ByteWindow<'_>, Diagnostic> {
64 let local = usize::try_from(start).map_err(|_| unrepresentable(start))?;
65 let bytes = self.as_bytes();
66 if local >= bytes.len() {
67 return Ok(ByteWindow::new(start, &[]));
68 }
69 let end = local.saturating_add(len).min(bytes.len());
70 Ok(ByteWindow::new(start, &bytes[local..end]))
71 }
72
73 fn len_hint(&self) -> Option<u64> {
74 u64::try_from(self.len()).ok()
75 }
76}
77
78#[derive(Debug)]
80pub struct WindowedFile {
81 file: File,
82 length: u64,
83 buffer: Box<[u8]>,
84 _reservation: MemoryReservation,
85}
86
87impl WindowedFile {
88 pub fn open(
95 path: impl AsRef<Path>,
96 max_window_bytes: usize,
97 context: &ExecutionContext,
98 ) -> Result<Self, Diagnostic> {
99 if max_window_bytes == 0 {
100 return Err(Diagnostic::new(Code::E1902).with_context("requested bytes", "0"));
101 }
102 let reservation = context.try_reserve(max_window_bytes).map_err(|error| {
103 Diagnostic::new(Code::E1902).with_context("reason", error.to_string())
104 })?;
105 let file = File::open(path.as_ref())
106 .map_err(|error| io_failure("source file could not be opened", &error))?;
107 let length = file
108 .metadata()
109 .map_err(|error| io_failure("source length could not be read", &error))?
110 .len();
111 let mut buffer = Vec::new();
112 buffer
113 .try_reserve_exact(max_window_bytes)
114 .map_err(|error| {
115 Diagnostic::new(Code::E1901).with_context("reason", error.to_string())
116 })?;
117 buffer.resize(max_window_bytes, 0);
118 Ok(Self {
119 file,
120 length,
121 buffer: buffer.into_boxed_slice(),
122 _reservation: reservation,
123 })
124 }
125
126 #[must_use]
128 pub const fn capacity(&self) -> usize {
129 self.buffer.len()
130 }
131}
132
133impl SourceBytes for WindowedFile {
134 fn window(&mut self, start: u64, len: usize) -> Result<ByteWindow<'_>, Diagnostic> {
135 if len > self.buffer.len() {
136 return Err(Diagnostic::new(Code::E1902)
137 .with_context("requested bytes", len.to_string())
138 .with_context("window bytes", self.buffer.len().to_string()));
139 }
140 if start >= self.length || len == 0 {
141 return Ok(ByteWindow::new(start, &[]));
142 }
143 self.file
144 .seek(SeekFrom::Start(start))
145 .map_err(|error| io_failure("source window could not seek", &error))?;
146 let remaining = self.length - start;
147 let host_remaining = match usize::try_from(remaining) {
148 Ok(value) => value,
149 Err(_) => usize::MAX,
150 };
151 let target = len.min(host_remaining);
152 let mut filled = 0;
153 while filled < target {
154 match self.file.read(&mut self.buffer[filled..target]) {
155 Ok(0) => break,
156 Ok(count) => filled += count,
157 Err(error) if error.kind() == ErrorKind::Interrupted => {}
158 Err(error) => return Err(io_failure("source window could not be read", &error)),
159 }
160 }
161 Ok(ByteWindow::new(start, &self.buffer[..filled]))
162 }
163
164 fn len_hint(&self) -> Option<u64> {
165 Some(self.length)
166 }
167}
168
169fn io_failure(message: &'static str, error: &std::io::Error) -> Diagnostic {
170 Diagnostic::new(Code::E7101)
171 .with_message(message)
172 .with_context("reason", error.to_string())
173}
174
175fn unrepresentable(value: impl std::fmt::Display) -> Diagnostic {
176 Diagnostic::new(Code::E1903).with_context("value", value.to_string())
177}
178
179#[cfg(test)]
180#[path = "source_tests.rs"]
181mod tests;