use std::{fmt, io, rc::Rc};
use colored::Colorize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum TokenKind {
LeftSquare, RightSquare, LeftCurly, RightCurly, Comma, Bang, Tilde, Colon, Tag, From, Alias, String, Comment, EoF, }
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenKind::LeftSquare => write!(f, "["),
TokenKind::RightSquare => write!(f, "]"),
TokenKind::LeftCurly => write!(f, "{{"),
TokenKind::RightCurly => write!(f, "}}"),
TokenKind::Comma => write!(f, ","),
TokenKind::Bang => write!(f, "!"),
TokenKind::Tilde => write!(f, "~"),
TokenKind::Colon => write!(f, ":"),
TokenKind::Tag => write!(f, "a tag"),
TokenKind::From => write!(f, "a pipeline"),
TokenKind::Alias => write!(f, "an alias"),
TokenKind::String => write!(f, "a string"),
TokenKind::Comment => write!(f, "a comment"),
TokenKind::EoF => write!(f, "end of file"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Position {
pub(super) s_line: usize,
pub(super) s_pos: usize,
pub(super) e_line: usize,
pub(super) e_pos: usize,
}
impl Position {
pub(super) fn new(s_line: usize, s_pos: usize, e_line: usize, e_pos: usize) -> Self {
Self { s_line, s_pos, e_line, e_pos }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub(super) kind: TokenKind,
pub(super) value: Rc<str>,
pub(super) position: Position,
}
impl Token {
pub(super) fn new(kind: TokenKind, value: &str, s_line: usize, start: usize, e_line:usize, end: usize) -> Self {
Self { kind, value: Rc::from(value), position: Position::new(s_line, start, e_line, end) }
}
}
#[derive(Default)]
pub(in super::super) struct OldLexer<'a> {
source: &'a [char],
l_num: usize,
l_pos: usize,
inside_square: bool,
inside_curly: bool,
}
impl<'a> OldLexer<'a> {
pub(crate) fn new(source: &'a [char]) -> Self {
Self { source, l_num: 1, l_pos: 0, inside_square: false, inside_curly: false }
}
fn has_more_chars(&self) -> bool { !self.source.is_empty() }
fn trim_whitespace(&mut self) {
while self.has_more_chars() && self.source[0].is_whitespace() {
self.advance();
}
}
fn chop(&mut self, n: usize) -> String {
let token = &self.source[0..n];
self.source = &self.source[n..];
self.l_pos += n;
token.iter().collect()
}
fn chop_while<P>(&mut self, mut predicate: P) -> String where P: FnMut(&char) -> bool {
let mut n = 0;
let mut n_pos = 0;
while n < self.source.len() && predicate(&self.source[n]) {
if self.source[n] == '\n' {
self.l_num += 1;
self.l_pos = 0;
n_pos = n;
}
n += 1;
}
let s = self.chop(n);
self.l_pos -= n_pos;
s
}
fn curr_char(&self) -> char {
if self.has_more_chars() {
self.source[0]
} else {
'\0'
}
}
fn next_char(&self) -> char {
if self.source.len() > 1 {
self.source[1]
} else {
'\0'
}
}
fn advance(&mut self) {
if self.curr_char() == '\r' && self.next_char() == '\n' {
self.source = &self.source[2..];
self.l_num += 1;
self.l_pos = 0;
} else if self.curr_char() == '\n' {
self.source = &self.source[1..];
self.l_num += 1;
self.l_pos = 0;
} else {
self.source = &self.source[1..];
self.l_pos += 1;
}
}
fn get_bracket(&mut self) -> Result<Option<Token>, io::Error> {
let start_line = self.l_num;
let start = self.l_pos;
let tokenkind: TokenKind;
let value: &str;
match self.curr_char() {
'}' => { tokenkind = TokenKind::RightCurly; value = "}"; self.inside_curly = false },
']' => { tokenkind = TokenKind::RightSquare; value = "]"; self.inside_square = false },
'{' => {
if self.inside_curly {
return Err(self.error(format!("Unclosed brackets at {}:{}", self.l_num, self.l_pos)))
}
tokenkind = TokenKind::LeftCurly; value = "{"; self.inside_curly = true
},
'[' => {
if self.inside_square {
return Err(self.error(format!("Unclosed brackets at {}:{}", self.l_num, self.l_pos)))
}
tokenkind = TokenKind::LeftSquare; value = "["; self.inside_square = true
},
_ => return Ok(None)
}
self.advance();
Ok(Some(Token::new(tokenkind, value, start_line, start, self.l_num, self.l_pos)))
}
fn get_special(&mut self) -> Result<Option<Token>, io::Error> {
let start = self.l_pos;
let start_line = self.l_num;
let tokenkind: TokenKind;
let value = match self.curr_char() {
',' => { tokenkind = TokenKind::Comma; self.chop(1) },
':' => { tokenkind = TokenKind::Colon; self.chop(1) },
'~' => { tokenkind = TokenKind::Tilde; self.chop(1) },
'!' => { tokenkind = TokenKind::Bang; self.chop(1) },
_ => return Ok(None)
};
Ok(Some(Token::new(tokenkind, &value, start_line, start, self.l_num, self.l_pos)))
}
fn get_comment(&mut self) -> Result<Option<Token>, io::Error> {
if self.curr_char() != '#' { return Ok(None) }
let start_line = self.l_num;
let start = self.l_pos;
let mut buffer = self.chop_while(|x| *x != '\n');
if buffer.ends_with('\r') {
buffer.pop();
}
Ok(Some(Token::new(TokenKind::Comment, &buffer, start_line, start, self.l_num, self.l_pos)))
}
fn get_string(&mut self) -> Result<Option<Token>, io::Error> {
if self.curr_char() != '"' { return Ok(None) }
self.advance();
let s_line = self.l_num;
let start = self.l_pos;
let buffer = self.chop_while(|x| *x != '"' && *x != '\n');
match self.curr_char() {
'"' => self.advance(),
_ => return Err(self.error(format!("Unclosed string at {}:{}", self.l_num, self.l_pos))),
}
if buffer.is_empty() {
return Err(self.error(format!("Empty string at {}:{}", self.l_num, self.l_pos)))
}
Ok(Some(Token::new(TokenKind::String, &buffer, s_line, start, self.l_num, self.l_pos)))
}
fn is_tag_char(x: &char) -> bool {
!(x.is_whitespace() || *x == '[' || *x == '@' || *x == '%' || *x == '$' || *x == '#'|| *x == ':' )
}
fn get_alias(&mut self) -> io::Result<Option<Token>> {
if self.curr_char() != '$' { return Ok(None) }
self.advance();
let s_line = self.l_num;
let start = self.l_pos;
let buffer = self.chop_while(OldLexer::is_tag_char);
if buffer.is_empty() {
return Err(self.error(format!("Empty alias at {}:{}", self.l_num, self.l_pos)))
}
Ok(Some(Token::new(TokenKind::Alias, &buffer, s_line, start, self.l_num, self.l_pos)))
}
fn get_from(&mut self) -> io::Result<Option<Token>> {
if self.curr_char() != '%' { return Ok(None) }
self.advance();
let s_line = self.l_num;
let start = self.l_pos;
let buffer = self.chop_while(OldLexer::is_tag_char);
if buffer.is_empty() {
return Err(self.error(format!("Empty pipeline at {}:{}", self.l_num, self.l_pos)))
}
Ok(Some(Token::new(TokenKind::From, &buffer, s_line, start, self.l_num, self.l_pos)))
}
fn get_tag(&mut self) -> io::Result<Option<Token>> {
if self.curr_char() != '@' { return Ok(None) }
self.advance();
let s_line = self.l_num;
let start = self.l_pos;
let buffer = self.chop_while(OldLexer::is_tag_char);
if buffer.is_empty() {
return Err(self.error(format!("Empty tag at {}:{}", self.l_num, self.l_pos)))
}
Ok(Some(Token::new(TokenKind::Tag, &buffer, s_line, start, self.l_num, self.l_pos)))
}
fn get_next_token(&mut self) -> Result<Token, io::Error> {
self.trim_whitespace();
if !self.has_more_chars() { return Ok(Token::new(TokenKind::EoF, "", self.l_num, self.l_pos, self.l_num,self.l_pos+1)) }
if let Some(tag) = self.get_tag()? { return Ok(tag) }
if let Some(frm) = self.get_from()? { return Ok(frm) }
if let Some(als) = self.get_alias()? { return Ok(als) }
if let Some(com) = self.get_comment()? { return Ok(com) }
if let Some(spc) = self.get_special()? { return Ok(spc) }
if let Some(bkt) = self.get_bracket()? { return Ok(bkt) }
if let Some(str) = self.get_string()? { return Ok(str) }
if self.curr_char().is_alphanumeric() {
Err(self.error(format!("Undelimited string at {}:{}", self.l_num, self.l_pos)))
} else {
Err(self.error(format!("Unknown character at {}:{}", self.l_num, self.l_pos)))
}
}
fn error(&self, message: String) -> io::Error {
let message = format!("{}: {}", "Config Error".bright_red(), message);
io::Error::other(message)
}
pub fn tokenise(&mut self) -> io::Result<Vec<Token>> {
let mut tokens = Vec::new();
loop {
let next = self.get_next_token()?;
if let TokenKind::EoF = next.kind {
tokens.push(next);
break
}
tokens.push(next);
}
Ok(tokens)
}
}