use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
use crate::engine::filesystem::SourceReference;
use crate::engine::filesystem::{File, FileLineSource};
use crate::engine::mouth::strings::{InputTokenizer, MouthState};
use crate::engine::state::State;
use crate::engine::utils::outputs::Outputs;
use crate::engine::{EngineAux, EngineReferences, EngineTypes};
use crate::prelude::{CommandCode, TokenList};
use crate::tex::catcodes::CategoryCodeScheme;
use crate::tex::characters::StringLineSource;
use crate::tex::tokens::control_sequences::CSName;
use crate::tex::tokens::token_lists::MacroExpansion;
use crate::tex::tokens::Token;
use crate::utils::errors::{InvalidCharacter, RecoverableError, TeXError, TeXResult};
pub mod strings;
pub trait Mouth<ET: EngineTypes> {
fn new(aux: &mut EngineAux<ET>, state: &mut ET::State) -> Self;
fn push_file(&mut self, f: ET::File);
fn push_string(&mut self, s: StringLineSource<ET::Char>);
fn push_exp(&mut self, exp: &TokenList<ET::Token>);
fn push_vec(&mut self, exp: Vec<ET::Token>);
fn push_slice_rev(&mut self, exp: &[ET::Token]);
fn push_macro_exp(&mut self, exp: MacroExpansion<ET::Token>);
fn requeue(&mut self, t: ET::Token);
fn get_next(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
) -> Result<Option<ET::Token>, InvalidCharacter<ET::Char>>;
fn iterate<R, E, F: FnMut(&mut EngineAux<ET>, ET::Token) -> TeXResult<Option<R>, ET>>(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
cont: F,
eof: E,
) -> TeXResult<R, ET>
where
E: Fn(&EngineAux<ET>, &ET::State, &mut Self) -> TeXResult<(), ET>;
fn endinput(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
) -> Result<(), InvalidCharacter<ET::Char>>;
fn finish(&mut self);
fn current_sourceref(&self) -> SourceReference<<ET::File as File>::SourceRefID>;
fn start_ref(&self) -> SourceReference<<ET::File as File>::SourceRefID>;
fn update_start_ref(&mut self);
fn line_number(&self) -> usize;
fn get_args(&mut self) -> [Vec<ET::Token>; 9];
fn return_args(&mut self, args: [Vec<ET::Token>; 9]);
fn read_until_endgroup<E, F: FnMut(&mut EngineAux<ET>, ET::Token) -> TeXResult<(), ET>>(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
mut cont: F,
eof: E,
) -> TeXResult<ET::Token, ET>
where
E: Fn(&EngineAux<ET>, &ET::State, &mut Self) -> TeXResult<(), ET>,
{
let mut ingroups = 0;
self.iterate(
aux,
state,
|a, t| {
match t.command_code() {
CommandCode::BeginGroup => ingroups += 1,
CommandCode::EndGroup => {
if ingroups == 0 {
return Ok(Some(t));
}
ingroups -= 1;
}
CommandCode::Primitive if t.is_primitive() == Some(PRIMITIVES.noexpand) => {
return Ok(None)
}
_ => (),
}
cont(a, t)?;
Ok(None)
},
eof,
)
}
fn preview(
&self,
int: &<ET::CSName as CSName<ET::Char>>::Handler,
cc: &CategoryCodeScheme<ET::Char>,
esc: Option<ET::Char>,
) -> String;
fn file_trace(
&self,
) -> impl Iterator<Item = SourceReference<<ET::File as File>::SourceRefID>> + '_;
}
enum TokenSource<T: Token, F: File<Char = T::Char>> {
String(InputTokenizer<T::Char, StringLineSource<T::Char>>),
File(InputTokenizer<T::Char, F::LineSource>, F::SourceRefID),
Vec(Vec<T>),
}
pub struct DefaultMouth<ET: EngineTypes> {
inputs: Vec<TokenSource<ET::Token, ET::File>>,
args: Option<[Vec<ET::Token>; 9]>,
start_ref: Vec<SourceReference<<ET::File as File>::SourceRefID>>,
vecs: Vec<Vec<ET::Token>>,
}
impl<ET: EngineTypes> Mouth<ET> for DefaultMouth<ET> {
fn new(_aux: &mut EngineAux<ET>, _state: &mut ET::State) -> Self {
Self {
inputs: Vec::new(),
args: Some(array_init::array_init(|_| Vec::new())),
start_ref: vec![],
vecs: vec![],
}
}
fn finish(&mut self) {
for s in self.inputs.drain(..) {
if let TokenSource::Vec(mut v) = s {
v.clear();
self.vecs.push(v);
}
}
self.start_ref.clear();
}
fn file_trace(
&self,
) -> impl Iterator<Item = SourceReference<<ET::File as File>::SourceRefID>> + '_ {
self.inputs.iter().rev().filter_map(|s| {
if let TokenSource::File(f, id) = s {
Some(SourceReference {
file: *id,
line: f.line(),
column: f.column(),
})
} else {
None
}
})
}
fn current_sourceref(&self) -> SourceReference<<ET::File as File>::SourceRefID> {
for s in self.inputs.iter().rev() {
if let TokenSource::File(f, id) = s {
return SourceReference {
file: *id,
line: f.line(),
column: f.column(),
};
}
}
self.start_ref.last().copied().unwrap_or_default()
}
fn start_ref(&self) -> SourceReference<<ET::File as File>::SourceRefID> {
self.start_ref.last().copied().unwrap_or_default()
}
fn update_start_ref(&mut self) {
match self.inputs.last() {
Some(TokenSource::File(f, id)) => {
let rf = SourceReference {
file: *id,
line: f.line(),
column: f.column(),
};
match self.start_ref.last_mut() {
None => self.start_ref.push(rf),
Some(s) => *s = rf,
}
}
Some(TokenSource::Vec(v)) if v.is_empty() => {
if let Some(TokenSource::Vec(v)) = self.inputs.pop() {
self.vecs.push(v);
} else {
unreachable!()
}
self.update_start_ref();
}
_ => (),
}
}
fn get_args(&mut self) -> [Vec<ET::Token>; 9] {
match std::mem::take(&mut self.args) {
Some(a) => a,
None => unreachable!(), }
}
fn return_args(&mut self, mut exp: [Vec<ET::Token>; 9]) {
for a in &mut exp {
a.clear();
}
self.args = Some(exp);
}
fn endinput(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
) -> Result<(), InvalidCharacter<ET::Char>> {
for (i, s) in self.inputs.iter().enumerate().rev() {
if let TokenSource::File(f, _) = s {
aux.outputs.file_close(f.source.path().display());
let TokenSource::File(mut r, _) = self.inputs.remove(i) else {
unreachable!()
};
self.start_ref.pop();
if r.state != MouthState::NewLine {
let mut ret = Vec::new();
let par = state.get_par_token();
match r.read(
aux.memory.cs_interner_mut(),
state.get_catcode_scheme(),
state.get_endline_char(),
&par,
|t| ret.push(t),
) {
Ok(_) => (),
Err(e) => e.recover(aux, state, self)?,
}
self.with_list(|ls| ls.extend(ret.into_iter().rev()));
}
return Ok(());
}
}
Ok(())
}
fn line_number(&self) -> usize {
for s in self.inputs.iter().rev() {
if let TokenSource::File(s, _) = s {
return s.line();
}
}
0
}
fn push_macro_exp(&mut self, mut exp: MacroExpansion<ET::Token>) {
self.with_list(|v| exp.consume_rev(v));
self.return_args(exp.args);
}
fn push_exp(&mut self, exp: &TokenList<ET::Token>) {
self.with_list(|v| v.extend(exp.0.iter().rev().cloned()));
}
fn push_vec(&mut self, exp: Vec<ET::Token>) {
self.with_list(|v| v.extend(exp.into_iter().rev()));
}
fn push_slice_rev(&mut self, exp: &[ET::Token]) {
self.with_list(|v| v.extend(exp.iter().cloned()));
}
fn push_string(&mut self, s: StringLineSource<ET::Char>) {
self.inputs
.push(TokenSource::String(InputTokenizer::new(s)));
}
fn requeue(&mut self, t: ET::Token) {
self.with_list(|v| v.push(t));
}
fn push_file(&mut self, f: ET::File) {
let id = f.sourceref();
let s = match f.line_source() {
Ok(s) => s,
Err(p) => panic!("File {} has no line source", p.display()),
};
let rf = SourceReference {
file: id,
line: 0,
column: 0,
};
self.inputs
.push(TokenSource::File(InputTokenizer::new(s), id));
self.start_ref.push(rf);
}
fn get_next(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
) -> Result<Option<ET::Token>, InvalidCharacter<ET::Char>> {
while let Some(src) = self.inputs.last_mut() {
match src {
TokenSource::Vec(v) => match v.pop() {
Some(t) => return Ok(Some(t)),
_ => {
if let Some(TokenSource::Vec(v)) = self.inputs.pop() {
self.vecs.push(v);
}
}
},
TokenSource::String(s) => {
let par = state.get_par_token();
match s.get_next(
aux.memory.cs_interner_mut(),
state.get_catcode_scheme(),
state.get_endline_char(),
&par,
) {
Ok(Some(t)) => return Ok(Some(t)),
Ok(None) => return Ok(Some(self.end_file(aux, state))),
Err(c) => {
c.recover::<_, InvalidCharacter<_>>(aux, state, self)?;
continue;
}
}
}
TokenSource::File(s, _) => {
let cc: &CategoryCodeScheme<ET::Char> = state.get_catcode_scheme();
let endline: Option<ET::Char> = state.get_endline_char();
let par = state.get_par_token();
match s.get_next(aux.memory.cs_interner_mut(), cc, endline, &par) {
Ok(Some(t)) => return Ok(Some(t)),
Ok(None) => return Ok(Some(self.end_file(aux, state))),
Err(c) => {
c.recover::<_, InvalidCharacter<_>>(aux, state, self)?;
continue;
}
}
}
}
}
Ok(None)
}
fn iterate<R, E, F: FnMut(&mut EngineAux<ET>, ET::Token) -> TeXResult<Option<R>, ET>>(
&mut self,
aux: &mut EngineAux<ET>,
state: &ET::State,
mut cont: F,
eof: E,
) -> TeXResult<R, ET>
where
E: Fn(&EngineAux<ET>, &ET::State, &mut Self) -> TeXResult<(), ET>,
{
'top: loop {
match self.inputs.last_mut() {
Some(TokenSource::Vec(v)) => {
while let Some(t) = v.pop() {
if let Some(r) = cont(aux, t)? {
return Ok(r);
}
}
if let Some(TokenSource::Vec(v)) = self.inputs.pop() {
self.vecs.push(v);
} else {
unreachable!()
}
}
Some(TokenSource::String(s)) => {
let cc = state.get_catcode_scheme();
let endline = state.get_endline_char();
let par = state.get_par_token();
loop {
match s.get_next(aux.memory.cs_interner_mut(), cc, endline, &par) {
Ok(Some(t)) => {
if let Some(r) = cont(aux, t)? {
return Ok(r);
}
}
Err(c) => {
c.recover::<_, TeXError<_>>(aux, state, self)?;
continue 'top;
}
Ok(None) => {
eof(aux, state, self)?;
continue 'top;
}
}
}
}
Some(TokenSource::File(s, _)) => {
let cc = state.get_catcode_scheme();
let endline = state.get_endline_char();
let par = state.get_par_token();
loop {
match s.get_next(aux.memory.cs_interner_mut(), cc, endline, &par) {
Ok(Some(t)) => {
if let Some(r) = cont(aux, t)? {
return Ok(r);
}
}
Err(c) => {
c.recover::<_, TeXError<_>>(aux, state, self)?;
continue 'top;
}
Ok(None) => {
eof(aux, state, self)?;
continue 'top;
}
}
}
}
None => eof(aux, state, self)?,
}
}
}
fn preview(
&self,
int: &<ET::CSName as CSName<ET::Char>>::Handler,
cc: &CategoryCodeScheme<ET::Char>,
esc: Option<ET::Char>,
) -> String {
let mut str = String::new();
for src in self.inputs.iter().rev() {
match src {
TokenSource::String(s) => s.preview(&mut 1000, &mut str).unwrap(),
TokenSource::File(s, _) => {
s.preview(&mut 1000, &mut str).unwrap();
break;
}
TokenSource::Vec(v) => {
for t in v.iter().rev().take(1000) {
t.display_fmt(int, cc, esc, &mut str).unwrap()
}
}
}
}
str
}
}
impl<ET: EngineTypes> DefaultMouth<ET> {
pub fn into<ET2: EngineTypes<Token = ET::Token, File = ET::File>>(self) -> DefaultMouth<ET2> {
DefaultMouth {
inputs: self.inputs,
args: self.args,
start_ref: self.start_ref,
vecs: self.vecs,
}
}
pub fn into_tokens<
ET2: EngineTypes<Char = ET::Char, File = ET::File>,
F: FnMut(ET::Token) -> ET2::Token,
>(
self,
mut token: F,
) -> DefaultMouth<ET2> {
DefaultMouth {
inputs: self
.inputs
.into_iter()
.map(|s| match s {
TokenSource::String(s) => TokenSource::String(s),
TokenSource::File(s, id) => TokenSource::File(s, id),
TokenSource::Vec(v) => {
TokenSource::Vec(v.into_iter().map(&mut token).collect())
}
})
.collect(),
args: self.args.map(|[a0, a1, a2, a3, a4, a5, a6, a7, a8]| {
[
a0.into_iter().map(&mut token).collect(),
a1.into_iter().map(&mut token).collect(),
a2.into_iter().map(&mut token).collect(),
a3.into_iter().map(&mut token).collect(),
a4.into_iter().map(&mut token).collect(),
a5.into_iter().map(&mut token).collect(),
a6.into_iter().map(&mut token).collect(),
a7.into_iter().map(&mut token).collect(),
a8.into_iter().map(&mut token).collect(),
]
}),
start_ref: self.start_ref,
vecs: self
.vecs
.into_iter()
.map(|v| v.into_iter().map(&mut token).collect())
.collect(),
}
}
fn with_list<Fn: FnOnce(&mut Vec<ET::Token>)>(&mut self, f: Fn) {
match self.inputs.last_mut() {
Some(TokenSource::Vec(v)) => f(v),
_ => {
let v = match self.vecs.pop() {
Some(mut v) => {
f(&mut v);
v
}
None => {
let mut v = Vec::new();
f(&mut v);
v
}
};
self.inputs.push(TokenSource::Vec(v));
}
};
}
fn end_file(&mut self, aux: &mut EngineAux<ET>, state: &ET::State) -> ET::Token {
match self.inputs.pop() {
Some(TokenSource::File(f, _)) => {
self.start_ref.pop();
aux.outputs.file_close(f.source.path().display());
}
Some(TokenSource::String(_)) => (), _ => unreachable!(),
};
let everyeof = state.get_primitive_tokens(PRIMITIVES.everyeof);
if everyeof.is_empty() {
ET::Token::eof()
} else {
self.requeue(ET::Token::eof());
self.push_exp(everyeof);
if let Some(TokenSource::Vec(v)) = self.inputs.last_mut() {
v.pop().unwrap()
} else {
unreachable!()
}
}
}
}
impl<ET: EngineTypes> EngineReferences<'_, ET> {
pub fn push_file(&mut self, f: ET::File) {
self.mouth.push_file(f);
}
pub fn push_every(&mut self, every: PrimitiveIdentifier) {
let tks = self.state.get_primitive_tokens(every);
self.mouth.push_exp(tks);
}
pub fn preview(&self) -> String {
self.mouth.preview(
self.aux.memory.cs_interner(),
self.state.get_catcode_scheme(),
self.state.get_escape_char(),
)
}
}