pub mod string_source;
pub mod methods;
use std::vec::IntoIter;
use log::debug;
use crate::engine::filesystem::File;
use crate::engine::mouth::string_source::StringSource;
use crate::engine::state::State;
use crate::file_end;
use crate::tex::catcodes::{CategoryCode, CategoryCodeScheme};
use crate::tex::token::{BaseToken, Token, TokenList, TokenWithSourceref};
use crate::utils::errors::{InvalidCharacter, OtherError, TeXError};
use crate::utils::Ptr;
use crate::utils::strings::CharType;
pub enum TeXMouthSource<T:Token> {
Token(TokenSource<T>),
NoExpand(T),
String(StringSource<T::Char>)
}
pub struct TokenSource<T:Token>(IntoIter<T>);
impl<T:Token> TokenSource<T> {
pub(crate) fn new(ls:Vec<T>) -> Self { Self(ls.into_iter()) }
pub fn get_next(&mut self) -> Option<T> { self.0.next() }
fn preview(&self) -> String {
let tks : Vec<T> = self.0.clone().collect();
TokenList(tks).to_string()
}
}
pub trait Mouth<T:Token>:Sized+'static {
fn new() -> Self;
fn push_tokens(&mut self,tks:Vec<T>);
fn push_file<F:File<T::Char>>(&mut self,file:&F);
fn requeue(&mut self,tk:T);
fn has_next<S:State<T>>(&mut self, state:&S) -> Result<bool,InvalidCharacter<T>>;
fn get_next<S:State<T>>(&mut self,state:&S) -> Result<Option<(T,bool)>,InvalidCharacter<T>>;
fn get_next_nopar<S:State<T>>(&mut self,state:&S) -> Result<Option<(T,bool)>,Box<dyn TeXError<T>>> {
match self.get_next(state)? {
Some((t,b)) => {
match t.base() {
BaseToken::CS(name) if *name == T::Char::par_token() =>
Err(OtherError{msg:format!("Paragraph ended while reading argument"),cause:Some(t),source:None}.into()),
BaseToken::Char(_,CategoryCode::EOF) =>
file_end!(),
_ => Ok(Some((t,b)))
}
}
o => Ok(o)
}
}
fn preview(&self,len:usize) -> String;
fn file_line(&self) -> String;
fn skip_whitespace<S:State<T>>(&mut self, state:&S) -> Result<(),InvalidCharacter<T>> {
methods::skip_whitespace(self,state)
}
fn skip_eq_char<S:State<T>>(&mut self,state:&S) -> Result<(),InvalidCharacter<T>> {
methods::skip_eq_char(self,state)
}
fn read_argument<S:State<T>>(&mut self, state:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
methods::read_argument(self,state)
}
fn read_argument_nopar<S:State<T>>(&mut self, state:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
methods::read_argument_nopar(self,state)
}
fn read_until_endgroup<S:State<T>>(&mut self, state:&S) -> Result<Vec<T>,Box<dyn TeXError<T>>> {
methods::read_until_endgroup(self,state)
}
}
pub struct NoTracingMouth<T:Token>{ sources:Vec<TeXMouthSource<T>>,buffer:Option<T>}
impl<T:Token> NoTracingMouth<T> {
}
impl<T:Token> Mouth<T> for NoTracingMouth<T> {
fn new() -> Self {
NoTracingMouth { sources:Vec::new(),buffer:None}
}
fn push_tokens(&mut self,tks:Vec<T>) {
self.sources.push(TeXMouthSource::Token(TokenSource::new(tks)))
}
fn push_file<F: File<T::Char>>(&mut self, file: &F) {
debug!("Pushing file {:?}", file.path());
self.sources.push(TeXMouthSource::String(StringSource::new(
file.content_string().clone(),
Some(Ptr::new(file.path().to_str().unwrap().to_string()))
)))
}
fn requeue(&mut self, tk: T) {
match self.buffer.take() {
Some(tk2) => {
self.push_tokens(vec!(tk2,tk))
},
None => self.buffer = Some(tk)
}
}
fn has_next<S:State<T>>(&mut self, state:&S) -> Result<bool,InvalidCharacter<T>> {
todo!("has_next for NoTracingMouth")
}
fn get_next<S:State<T>>(&mut self, state:&S) -> Result<Option<(T,bool)>,InvalidCharacter<T>> {
todo!("get_next for NoTracingMouth")
}
fn preview(&self,_len:usize) -> String { todo!("preview in NoTracingMouth") }
fn file_line(&self) -> String { "unknown source".to_string() }
}
pub struct TracingMouth<Char:CharType>{ sources:Vec<TeXMouthSource<TokenWithSourceref<Char>>>,buffer:Option<TokenWithSourceref<Char>>}
impl<Char:CharType> Mouth<TokenWithSourceref<Char>> for TracingMouth<Char> {
fn new() -> Self {
TracingMouth { sources:Vec::new(),buffer:None}
}
fn push_tokens(&mut self,mut tks:Vec<TokenWithSourceref<Char>>) {
match std::mem::take(&mut self.buffer) {
Some(t) => tks.push(t),
_ => ()
}
self.sources.push(TeXMouthSource::Token(TokenSource::new(tks)))
}
fn push_file<F: File<Char>>(&mut self, file: &F) {
debug!("Pushing file {:?}", file.path());
self.sources.push(TeXMouthSource::String(StringSource::new(
file.content_string(),
Some(Ptr::new(file.path().to_str().unwrap().to_string()))
)))
}
fn requeue(&mut self, tk: TokenWithSourceref<Char>) {
match self.buffer.take() {
Some(tk2) => {
self.push_tokens(vec!(tk2,tk))
},
None => self.buffer = Some(tk)
}
}
fn has_next<S:State<TokenWithSourceref<Char>>>(&mut self, state:&S) -> Result<bool,InvalidCharacter<TokenWithSourceref<Char>>> {
match self.buffer {
Some(_) => Ok(true),
_ => self.has_next_i(state.get_catcode_scheme(),state.get_endlinechar())
}
}
fn get_next<S:State<TokenWithSourceref<Char>>>(&mut self, s:&S) -> Result<Option<(TokenWithSourceref<Char>, bool)>,InvalidCharacter<TokenWithSourceref<Char>>> {
if let Some(tk) = std::mem::take(&mut self.buffer) { return Ok(Some((tk,true))) } else {
self.get_next_i(s)
}
}
fn preview(&self,len:usize) -> String {
let mut ret = String::new();
match &self.buffer {
Some(t) => ret.push_str(format!("{}",t).as_str()),
None => ()
}
if ret.len() > len { return ret.chars().take(len).collect() }
for s in self.sources.iter().rev() {
ret.push_str(&match s {
TeXMouthSource::NoExpand(t) => todo!("preview `\noexpand`ed token"),
TeXMouthSource::Token(ts) => ts.preview(),
TeXMouthSource::String(ss) => ss.preview()
});
if ret.len() > len { return ret.chars().take(len).collect() }
}
ret.chars().take(len).collect()
}
fn file_line(&self) -> String {
for s in self.sources.iter().rev() {
match s {
TeXMouthSource::String(ss) => {
match &ss.source {
Some(s) => return format!("{}:({},{})",s,ss.line(),ss.column()),
None => ()
}
}
_ => ()
}
}
"unknown source".to_string()
}
}
impl<C:CharType> TracingMouth<C> {
fn has_next_i(&mut self, cc:&CategoryCodeScheme<C>, endline: Option<C>) -> Result<bool,InvalidCharacter<TokenWithSourceref<C>>> {
if let Some(source) = self.sources.last_mut() {
return if let Some(tk) = match source {
TeXMouthSource::Token(ts) => ts.get_next(),
TeXMouthSource::String(ss) => ss.get_next(cc, endline)?,
TeXMouthSource::NoExpand(t) => return Ok(true)
} {
self.buffer = Some(tk);
Ok(true)
} else {
match source {
TeXMouthSource::String(_) => {
self.buffer = Some(TokenWithSourceref::<C>::new(BaseToken::Char(C::from(b'\n'),CategoryCode::EOF),None));
self.sources.pop();
Ok(true)
}
_ => {
self.sources.pop();
self.has_next_i(cc, endline)
}
}
}
} else { Ok(false) }
}
fn get_next_i<S:State<TokenWithSourceref<C>>>(&mut self, state:&S) -> Result<Option<(TokenWithSourceref<C>, bool)>,InvalidCharacter<TokenWithSourceref<C>>> {
if let Some(source) = self.sources.last_mut() {
return if let Some(tk) = match source {
TeXMouthSource::Token(ts) => ts.get_next(),
TeXMouthSource::String(ss) => ss.get_next(state.get_catcode_scheme(), state.get_endlinechar())?,
TeXMouthSource::NoExpand(_) => {
match self.sources.pop() {
Some(TeXMouthSource::NoExpand(t)) => return Ok(Some((t,false))),
_ => unreachable!()
}
}
} {
Ok(Some((tk,true)))
} else {
match source {
TeXMouthSource::String(_) => {
self.sources.pop();
debug!("file end; inserting \\everyeof");
let mut v = state.get_primitive_toks("everyeof");
if v.is_empty() {
Ok(Some((TokenWithSourceref::<C>::new(BaseToken::Char(C::from(b'\n'),CategoryCode::EOF),None),true)))
} else {
v.push(TokenWithSourceref::<C>::new(BaseToken::Char(C::from(b'\n'), CategoryCode::EOF),None));
self.push_tokens(v);
self.get_next_i(state)
}
}
_ => {
self.sources.pop();
self.get_next_i(state)
}
}
}
} else { Ok(None) }
}
}