use std::io::Read;
#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Token {
pub file: Option<String>,
pub line: u64,
pub text: String,
pub was_quoted: Option<char>,
pub in_snippet: bool,
pub snippet_name: String,
}
pub struct Tokenizer<'a> {
reader: &'a mut dyn std::io::Read,
pub file: Option<String>,
token: Token,
line: u64,
skipped_lines: u64,
}
impl Iterator for Tokenizer<'_> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
let mut val: String = String::new();
let mut comment: bool = false;
let mut quoted: bool = false;
let mut bt_quoted: bool = false;
let mut escaped: bool = false;
macro_rules! return_token {
() => {
self.token.text = val.clone();
self.token.was_quoted = None;
self.token.file = self.file.clone();
return Some(self.token.clone());
};
( $ch:expr ) => {
self.token.text = val.clone();
self.token.was_quoted = Some($ch);
self.token.file = self.file.clone();
return Some(self.token.clone());
};
}
while let Some(Ok(ch)) = self.reader.bytes().next() {
if self.line == 1 {
if ch == 0xEF {
let mut buf = [0; 2];
if let Some(Ok(ch)) = self.reader.bytes().next() {
buf[0] = ch;
}
if let Some(Ok(ch)) = self.reader.bytes().next() {
buf[1] = ch;
}
if buf == [0xBB, 0xBF] {
continue;
}
}
}
let ch = ch as char;
if self.line == 1 && val.is_empty() && ch == '\u{FEFF}' {
continue;
}
if !escaped && !bt_quoted && ch == '\\' {
escaped = true;
continue;
}
if quoted || bt_quoted {
if quoted && escaped {
if ch != '"' {
val.push('\\');
}
escaped = false;
} else {
if quoted && ch == '"' {
return_token!('"');
}
if bt_quoted && ch == '`' {
return_token!('`');
}
}
if ch == '\n' {
self.line += 1 + self.skipped_lines;
self.skipped_lines = 0;
}
val.push(ch);
continue;
}
if ch.is_whitespace() {
if ch == '\r' {
continue;
}
if ch == '\n' {
if escaped {
self.skipped_lines += 1;
escaped = false;
} else {
self.line += 1 + self.skipped_lines;
self.skipped_lines = 0;
}
comment = false;
}
if !val.is_empty() {
return_token!();
}
continue;
}
if ch == '#' && val.is_empty() {
comment = true;
}
if comment {
continue;
}
if val.is_empty() {
self.token = Token {
line: self.line,
..Default::default()
};
if ch == '"' {
quoted = true;
continue;
}
if ch == '`' {
bt_quoted = true;
continue;
}
}
if escaped {
val.push('\\');
escaped = false
}
val.push(ch);
}
if !val.is_empty() {
return_token!();
} else {
None
}
}
}
impl<'a> Tokenizer<'a> {
pub fn new(reader: &'a mut dyn std::io::Read, file: Option<String>) -> Self {
Self {
reader,
file,
token: Token {
line: 1,
..Default::default()
},
line: 1,
skipped_lines: 0,
}
}
}