luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use std::{
    fs::File,
    io::{stdin, Read},
};

pub trait SliceExt {
    fn trim(&self) -> &Self;

    fn trim_end_matches(&self, m: u8) -> &Self;
}

impl SliceExt for [u8] {
    fn trim(&self) -> &[u8] {
        fn is_not_whitespace(c: &u8) -> bool {
            !matches!(c, b' ' | b'\x09'..=b'\x0d')
        }

        match (
            self.iter().position(is_not_whitespace),
            self.iter().rposition(is_not_whitespace),
        ) {
            (Some(start), Some(end)) => &self[start..end + 1],
            (..) => &[],
        }
    }

    fn trim_end_matches(&self, m: u8) -> &[u8] {
        match self.iter().rposition(|c| c != &m) {
            Some(end) => &self[..end + 1],
            None => &[],
        }
    }
}

pub fn read_lua_file(filename: &str) -> Result<Vec<u8>, std::io::Error> {
    let mut chunk = Vec::new();
    if filename == "-" {
        // Read from stdin
        stdin().read_to_end(&mut chunk)?;
    } else {
        let mut file = File::open(&filename)?;
        file.read_to_end(&mut chunk)?;
    }

    // Skip BOM
    if chunk.len() >= 3 && chunk[0] == 0xEF && chunk[1] == 0xBB && chunk[2] == 0xBF {
        chunk.drain(0..3);
    }

    let mut iter = chunk.iter().enumerate();
    // If file starts with '#', skip first line
    if matches!(iter.next(), Some((_, &b'#'))) {
        loop {
            match iter.next() {
                Some((pos, c)) if c == &b'\n' => {
                    if chunk[pos + 1..].starts_with(&[b'\x1b']) {
                        // Do not keep newline in binary file
                        chunk.drain(0..=pos);
                    } else {
                        // Keep newline for correct line count
                        chunk.drain(0..pos);
                    }
                    break;
                }
                None => return Ok(Vec::new()),
                _ => {}
            }
        }
    }

    Ok(chunk)
}