#![allow(dead_code)]
mod token_match;
mod compare;
pub mod tokenize;
pub mod parse;
pub mod prelude;
use std::fmt;
use std::hash::Hash;
use colored::Colorize;
use parse::Grammar;
use tokenize::Alphabet;
use trees::Tree;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextPosition {
pub line: usize,
pub char: usize,
pub index: usize
}
impl TextPosition {
pub fn new(line: usize, char: usize, index: usize) -> Self {
TextPosition { line, char, index }
}
}
impl fmt::Display for TextPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.char)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ASTNode<PN: fmt::Debug + PartialEq + Copy> {
pub proc: PN,
pub text: String,
pub pos: TextPosition
}
impl<PN: fmt::Debug + PartialEq + Copy> ASTNode<PN> {
pub fn new(proc: PN, text: String, pos: TextPosition) -> Self {
ASTNode { proc, text, pos }
}
}
impl<PN: fmt::Debug + fmt::Display + PartialEq + Copy> fmt::Display for ASTNode<PN> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}[\"{}\"]", self.proc, self.text)
}
}
pub type AST<PN> = Tree<ASTNode<PN>>;
#[derive(Debug, PartialEq, Eq)]
pub struct ParsingError {
pub message: String,
pub pos: TextPosition,
pub source: Option<String>
}
impl ParsingError {
pub fn new(message: String, pos: TextPosition, source: Option<String>) -> Self {
ParsingError { message, pos, source }
}
fn find_src_offset_index(&self, source: &String, start: isize, incr: isize, max: isize) -> (usize, bool) {
let mut offset = start;
let mut ellipsis = false;
loop {
let char = source.chars().nth((self.pos.index as isize + offset) as usize);
if char.is_none() || char == Some('\n') {
offset -= incr;
break;
}
offset += incr;
if offset == max {
ellipsis = true;
break;
}
}
((self.pos.index as isize + offset) as usize, ellipsis)
}
fn format_source_pointer(&self, f: &mut fmt::Formatter<'_>, source: &String) -> fmt::Result {
let (first_index, start_ellipsis) = self.find_src_offset_index(source, -1, -1, -20);
let (last_index, end_ellipsis) = self.find_src_offset_index(source, 0, 1, 19);
if start_ellipsis { write!(f, "...")?; }
write!(f, "{}", &source[first_index .. last_index + 1])?;
if end_ellipsis { write!(f, "...")?; }
writeln!(f)?;
let mut pointer_offset = self.pos.index - first_index;
if start_ellipsis { pointer_offset += 3 }
write!(f, "{}", " ".repeat(pointer_offset))?;
writeln!(f, "{}", "^".bold().red())
}
}
impl fmt::Display for ParsingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}: {} ({})", "Parsing error".red().bold(), self.message, self.pos)?;
if let Some(source) = &self.source {
writeln!(f)?;
self.format_source_pointer(f, source)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct Parser<PN: Eq + Hash + Copy + fmt::Display + fmt::Debug, TN: Eq + Copy + fmt::Display + fmt::Debug> {
alphabet: Alphabet<TN>,
grammar: Grammar<PN, TN>
}
impl<PN: Eq + Hash + Copy + fmt::Display + fmt::Debug, TN: Eq + Copy + fmt::Display + fmt::Debug> Parser<PN, TN> {
pub fn new(alphabet: Alphabet<TN>, grammar: Grammar<PN, TN>) -> Self {
Parser { alphabet, grammar }
}
pub fn parse(&self, proc: PN, text: String) -> Result<AST<PN>, ParsingError> {
let result = self.alphabet.tokenize(text.clone()).and_then(|tokens| {
self.grammar.parse(proc, &tokens)
});
match result {
Ok(ast) => Ok(ast),
Err(mut error) => {
error.source = Some(text);
Err(error)
}
}
}
}
#[cfg(test)]
mod tests {
use colored::Colorize;
use crate::{ParsingError, TextPosition};
#[test]
fn parsing_error_with_small_char_should_format_properly() {
let parsing_error = ParsingError::new(
"Error happened here".to_string(),
TextPosition::new(2, 1, 5),
Some("123\n456789\nsgfde".to_string())
);
assert_eq!(parsing_error.to_string(), format!("{}: Error happened here (2:1)\n\n456789\n {}\n", "Parsing error".red().bold(), "^".red().bold()));
}
#[test]
fn parsing_error_should_format_properly() {
let parsing_error = ParsingError::new(
"Error happened here".to_string(),
TextPosition::new(2, 5, 9),
Some("123\n456789\nsgfde".to_string())
);
assert_eq!(parsing_error.to_string(), format!("{}: Error happened here (2:5)\n\n456789\n {}\n", "Parsing error".red().bold(), "^".red().bold()));
}
#[test]
fn parsing_error_should_format_ellipses_properly() {
let parsing_error = ParsingError::new(
"Error happened here".to_string(),
TextPosition::new(2, 43, 48),
Some("123\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa456789aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nsgfde".to_string())
);
assert_eq!(parsing_error.to_string(), format!("{}: Error happened here (2:43)\n\n...aaaaaaaaaaaaaaaa456789aaaaaaaaaaaaaaaaaa...\n {}\n", "Parsing error".red().bold(), "^".red().bold()));
}
}