parseal 0.2.4

An easy way to create your own parsers
Documentation
use rand::random;
use std::{fmt, vec::IntoIter};

use super::ParseError;

#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Position {
    pub column: usize,
    pub row: usize,
    pub index: usize,
    pub file: Option<String>,
    pub file_id: u32,
}

impl Position {
    pub fn end(value: &str, file: Option<String>, file_id: u32) -> Position {
        let mut column = 0;
        let mut row = 0;

        for c in value.chars() {
            match c {
                '\n' => {
                    column = 0;
                    row += 1;
                }
                _ => column += 1,
            }
        }

        Self {
            column,
            row,
            index: value.len(),
            file,
            file_id,
        }
    }
}

impl PartialOrd for Position {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        if other == &Position::default() {
            return Some(std::cmp::Ordering::Greater);
        }
        if self == &Position::default() {
            return Some(std::cmp::Ordering::Less);
        }

        match self.file.partial_cmp(&other.file) {
            Some(std::cmp::Ordering::Equal) => {}
            _ => return None,
        }
        match self.file_id.partial_cmp(&other.file_id) {
            Some(std::cmp::Ordering::Equal) => {}
            _ => return None,
        }

        match self.row.partial_cmp(&other.row) {
            Some(std::cmp::Ordering::Equal) => {}
            ord => return ord,
        }
        self.index.partial_cmp(&other.index)
    }
}

impl fmt::Display for Position {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match &self.file {
            Some(file) => write!(f, "{}:{}:{}", file, self.row, self.column),
            None => write!(f, "{}:{}", self.row, self.column),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Span {
    pub start: Position,
    pub end: Position,
}

impl Span {
    pub fn new(start: Position, end: Position) -> Self {
        Self { start, end }
    }
}

impl PartialOrd for Span {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        if other == &Span::default() {
            return Some(std::cmp::Ordering::Greater)
        }
        if self == &Span::default() {
            return Some(std::cmp::Ordering::Less)
        }

        match self.start.partial_cmp(&other.start) {
            Some(std::cmp::Ordering::Less) => match self.end.partial_cmp(&other.end) {
                Some(std::cmp::Ordering::Less) | None => None,
                Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Greater) => {
                    Some(std::cmp::Ordering::Greater)
                }
            },
            Some(std::cmp::Ordering::Equal) => self.end.partial_cmp(&other.end),
            Some(std::cmp::Ordering::Greater) => match self.end.partial_cmp(&other.end) {
                Some(std::cmp::Ordering::Greater) | None => None,
                Some(std::cmp::Ordering::Equal) | Some(std::cmp::Ordering::Less) => {
                    Some(std::cmp::Ordering::Less)
                }
            },
            None => None,
        }
    }
}

impl fmt::Display for Span {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} - {}:{}", self.start, self.end.row, self.end.column)
    }
}

#[derive(Debug, Clone)]
pub enum WhitespaceType {
    Ignore,
    KeepAll,
    Indent,
}

pub struct CharStreamBuilder {
    buffer: String,
    file: Option<String>,
    file_id: u32,
    indent_size: u8,
}

impl CharStreamBuilder {
    pub fn new(buffer: String) -> Self {
        Self {
            buffer,
            file: None,
            file_id: random(),
            indent_size: 4,
        }
    }

    pub fn build(&mut self) -> CharStream {
        let buffer = self.buffer.clone();
        let chars = buffer.chars().collect::<Vec<_>>().into_iter();
        let file = self.file.clone();
        let eof = Position::end(&buffer, file.clone(), self.file_id);

        CharStream {
            chars,
            file,
            file_id: self.file_id,
            column: 0,
            row: 0,
            index: 0,
            eof,
            whitespace: WhitespaceType::Ignore,
            indent: 0,
            indent_size: self.indent_size,
            in_indent: true,
        }
    }
}

#[derive(Debug, Clone)]
pub struct CharStream {
    chars: IntoIter<char>,
    file: Option<String>,
    file_id: u32,
    column: usize,
    row: usize,
    index: usize,
    eof: Position,
    whitespace: WhitespaceType,
    indent: u8,
    indent_size: u8,
    in_indent: bool,
}

impl CharStream {
    pub fn new(value: String) -> CharStreamBuilder {
        CharStreamBuilder::new(value)
    }

    pub fn pos(&self) -> Position {
        Position {
            column: self.column,
            row: self.row,
            index: self.index,
            file: self.file.clone(),
            file_id: self.file_id,
        }
    }

    pub fn goto(&mut self, position: Position) -> Result<(), ParseError> {
        if self.file_id != position.file_id {
            return Err(ParseError(
                "Could not go to position in different buffer.".to_string(),
                position,
            ));
        }

        if position < self.pos() {
            return Err(ParseError(
                "Charstream does not support going back.".to_string(),
                position,
            ));
        }

        if position > self.eof {
            return Err(ParseError(
                "Charstream can not go to position after end of buffer.".to_string(),
                self.eof.clone(),
            ));
        }

        while self.pos() < position {
            self.next();
        }

        Ok(())
    }

    pub fn set_whitespace(&mut self, whitespace: WhitespaceType) {
        self.whitespace = whitespace;
    }

    pub fn indent(&self) -> u8 {
        self.indent
    }
}

impl Iterator for CharStream {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        let chr = match self.chars.next() {
            Some('\n') => {
                self.index += 1;
                self.column = 0;
                self.row += 1;
                Some('\n')
            }
            Some(value) => {
                self.index += 1;
                self.column += 1;
                Some(value)
            }
            None => None,
        };

        match self.whitespace {
            WhitespaceType::Ignore => match chr {
                Some(c) if c.is_whitespace() => self.next(),
                c => c,
            },
            WhitespaceType::KeepAll => chr,
            WhitespaceType::Indent => match chr {
                Some(c) if c.is_whitespace() => {
                    match c {
                        '\t' => {
                            if self.in_indent {
                                self.indent += self.indent_size;
                            }
                        }
                        ' ' => {
                            if self.in_indent {
                                self.indent += 1;
                            }
                        }
                        '\n' => {
                            self.in_indent = true;
                            self.indent = 0;
                        }
                        _ => {}
                    }
                    self.next()
                }
                c => {
                    self.in_indent = false;
                    c
                }
            },
        }
    }
}