use std::cell::RefCell;
use std::io::Read;
use crate::line_index::LineIndex;
#[derive(Debug)]
pub struct NullTerminatedBuf(Vec<u8>);
impl NullTerminatedBuf {
pub fn from_reader(reader: &mut dyn Read) -> Result<NullTerminatedBuf, std::io::Error> {
let mut v = Vec::<u8>::new();
reader.read_to_end(&mut v)?;
v.push(0);
Ok(NullTerminatedBuf(v))
}
pub fn from_file(f: &'_ mut std::fs::File) -> Result<NullTerminatedBuf, std::io::Error> {
Self::from_reader(f)
}
pub fn from_slice_copy(s: &[u8]) -> NullTerminatedBuf {
let mut v = Vec::with_capacity(s.len() + 1);
v.extend_from_slice(s);
v.push(0);
NullTerminatedBuf(v)
}
pub fn from_slice_check(s: &[u8]) -> NullTerminatedBuf {
Self::from_slice_copy(if let [head @ .., 0] = s { head } else { s })
}
pub fn from_str_copy(s: &str) -> NullTerminatedBuf {
Self::from_slice_copy(s.as_bytes())
}
pub fn from_str_check(s: &str) -> NullTerminatedBuf {
Self::from_slice_check(s.as_bytes())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
}
impl AsRef<[u8]> for NullTerminatedBuf {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
pub struct SourceBuffer {
name: String,
buf: NullTerminatedBuf,
line_index: RefCell<Option<LineIndex>>,
}
impl SourceBuffer {
pub fn from_str(name: impl Into<String>, contents: &str) -> SourceBuffer {
SourceBuffer {
name: name.into(),
buf: NullTerminatedBuf::from_str_copy(contents),
line_index: RefCell::new(None),
}
}
pub fn from_slice_check(name: impl Into<String>, contents: &[u8]) -> SourceBuffer {
SourceBuffer {
name: name.into(),
buf: NullTerminatedBuf::from_slice_check(contents),
line_index: RefCell::new(None),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn bytes(&self) -> &[u8] {
let raw = self.buf.as_bytes();
&raw[..raw.len() - 1]
}
pub fn raw(&self) -> &[u8] {
self.buf.as_bytes()
}
pub fn with_line_index<R>(&self, f: impl FnOnce(&LineIndex, &[u8]) -> R) -> R {
{
let mut slot = self.line_index.borrow_mut();
if slot.is_none() {
*slot = Some(LineIndex::build(self.bytes()));
}
}
let slot = self.line_index.borrow();
f(slot.as_ref().unwrap(), self.bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_terminated_and_named() {
let b = SourceBuffer::from_str("foo.js", "abc");
assert_eq!(b.name(), "foo.js");
assert_eq!(b.bytes(), b"abc");
assert_eq!(b.raw()[b.raw().len() - 1], 0u8);
}
#[test]
fn already_terminated_input_not_doubled() {
let b = SourceBuffer::from_slice_check("x", b"ab\0");
assert_eq!(b.bytes(), b"ab");
}
#[test]
fn empty_input_gives_empty_bytes() {
let b = SourceBuffer::from_str("empty.js", "");
assert_eq!(b.bytes(), b"");
assert_eq!(b.raw(), b"\0");
assert_eq!(b.raw().len(), b.bytes().len() + 1);
}
}