use crate::{Session, SJFLError};
use crate::err::Span;

mod assignment;
mod import;
mod list;
mod number;
mod statement;
mod string;
mod table;
mod value;
mod var;

pub use assignment::Assignment;
pub use import::Import;
pub use var::Variable;
pub use list::List;
pub use string::InternedString;
pub use table::Table;

pub trait Parse {
    // `ind` points to the first character of the object.
    // It doesn't check whether `ind` is in bound.
    // The returned `usize` points to the first character AFTER the object.
    // It might be out of `s`.
    fn parse(s: &[u8], ind: usize, session: &mut Session) -> Result<(Self, usize), SJFLError> where Self: Sized;
}

// initial `ind` must either be (1) first character of a value or a delimiter, (2) whitespace, or (3) start of a comment
// the returned value is always either be (1) EOF, or (2) first character of a value or a delimiter
// if the initial `ind` or the returned `ind` is not inside `s`, it returns `SJFLError::UnexpectedEof`
pub fn skip_whitespaces_and_comments(s: &[u8], mut ind: usize) -> Result<usize, SJFLError> {

    while ind < s.len() {

        if s[ind] == b' ' || s[ind] == b'\n' || s[ind] == b'\r' || s[ind] == b'\t' {
            ind += 1;
        }

        else if s[ind] == b'#' {
            ind += 1;

            while ind < s.len() && s[ind] != b'\n' {
                ind += 1;
            }

        }

        else {
            return Ok(ind);
        }

    }

    Err(SJFLError::UnexpectedEof(Span::from_index(ind)))
}

pub fn match_exact(s: &[u8], mut ind: usize, word: &[u8]) -> Result<usize, SJFLError> {

    if ind + word.len() > s.len() + 1 {
        return Err(SJFLError::UnexpectedEof(Span::from_index(ind)));
    }

    for c in word.iter() {

        if s[ind] != *c {
            return Err(SJFLError::UnexpectedChar(s[ind], Span::from_index(ind)));
        }

        ind += 1;
    }

    Ok(ind)
}