use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use core::ops::Range;
use kstring::KString;
use mathml_renderer::arena::Arena;
use crate::{
ParserConfig,
character_class::Class,
commands::resolve_builtin_cmd,
custom_cmds::{CmdSource, CustomCmds, RecordedToken},
error::{LatexErrKind, LatexError},
global_state::GlobalState,
lexer::{Lexer, LexerOutput},
string_pool::{InternedStr, StringPool},
token::{EndToken, Span, TokSpan, Token},
};
pub(super) struct TokenQueue<'state, 'arena> {
lexer: Lexer<'arena>,
pub stores: Stores<'state, 'arena>,
queue: VecDeque<QueuedTok<'arena>>,
cmd_names: StringPool,
lexer_is_eoi: bool,
next_non_whitespace: usize,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct QueuedTok<'source>(TokSpan, Option<&'source str>);
#[cfg(target_arch = "wasm32")]
static_assertions::assert_eq_size!(QueuedTok<'static>, [usize; 7]);
impl<'source> QueuedTok<'source> {
#[inline]
pub(super) const fn new(tokspan: TokSpan, name: Option<&'source str>) -> Self {
QueuedTok(tokspan, name)
}
#[inline]
pub(super) fn token(&self) -> &Token {
self.0.token()
}
#[inline]
fn tokspan(&self) -> &TokSpan {
&self.0
}
#[inline]
fn into_tokspan(self) -> TokSpan {
self.0
}
#[inline]
pub(super) fn span(&self) -> Span {
self.0.span()
}
#[inline]
pub(super) fn name(&self) -> Option<&'source str> {
self.1
}
#[inline]
fn with_token(&self, token: Token) -> Self {
QueuedTok(TokSpan::new(token, self.0.span()), self.1)
}
#[inline]
fn unwrap_math(self) -> Self {
let (tok, span) = self.0.into_parts();
QueuedTok(TokSpan::new(tok.unwrap_math(), span), self.1)
}
}
impl From<Token> for QueuedTok<'_> {
#[inline]
fn from(token: Token) -> Self {
QueuedTok(token.into(), None)
}
}
impl From<TokSpan> for QueuedTok<'_> {
#[inline]
fn from(tokspan: TokSpan) -> Self {
QueuedTok(tokspan, None)
}
}
impl From<QueuedTok<'_>> for TokSpan {
#[inline]
fn from(queued: QueuedTok<'_>) -> Self {
queued.0
}
}
pub(super) struct Stores<'state, 'arena> {
pub parser_cfg: &'arena ParserConfig,
pub global_state: &'state mut GlobalState,
pub local_cmds: CustomCmds,
}
impl Stores<'_, '_> {
fn resolve_command(&self, name: &str) -> Option<Token> {
let local = &self.local_cmds;
let document = &self.global_state.custom_cmds;
let config = &self.parser_cfg.custom_cmds_from_cfg;
if let Some(tok) = local
.get(name, CmdSource::Local)
.or_else(|| document.get(name, CmdSource::Document))
.or_else(|| config.get(name, CmdSource::Config))
{
return Some(tok);
}
resolve_builtin_cmd(self.parser_cfg, name)
}
fn get_body(&self, source: CmdSource, start: usize, end: usize) -> Option<&[RecordedToken]> {
match source {
CmdSource::Config => self.parser_cfg.custom_cmds_from_cfg.body(start, end),
CmdSource::Document => self.global_state.custom_cmds.body(start, end),
CmdSource::Local => self.local_cmds.body(start, end),
}
}
}
static EOI_TOK: QueuedTok<'static> =
QueuedTok::new(TokSpan::new(Token::Eoi, Span::zero_width(0)), None);
impl<'state, 'arena> TokenQueue<'state, 'arena> {
pub(super) fn new(
lexer: Lexer<'arena>,
parser_cfg: &'arena ParserConfig,
global_state: &'state mut GlobalState,
) -> Result<Self, Box<LatexError>> {
let mut tm = TokenQueue {
lexer,
stores: Stores {
parser_cfg,
global_state,
local_cmds: CustomCmds::default(),
},
queue: VecDeque::with_capacity(2),
cmd_names: StringPool::default(),
lexer_is_eoi: false,
next_non_whitespace: 0,
};
let idx = tm.load_token_skip_whitespace()?;
tm.next_non_whitespace = idx;
Ok(tm)
}
fn resolve_lexed(&mut self, lexed: LexerOutput<'arena>) -> QueuedTok<'arena> {
match lexed {
LexerOutput::Token(tokspan) => tokspan.into(),
LexerOutput::CommandName(name, span) => {
let tok = self
.stores
.resolve_command(name)
.unwrap_or_else(|| Token::UnresolvedCommand(self.cmd_names.intern(name)));
QueuedTok::new(TokSpan::new(tok, span), Some(name))
}
}
}
#[inline]
pub(super) fn cmd_name(&self, name: InternedStr) -> &str {
self.cmd_names.get(name)
}
fn load_token_skip_whitespace(&mut self) -> Result<usize, Box<LatexError>> {
Ok(self
.load_token(is_not_whitespace)?
.unwrap_or(self.queue.len()))
}
fn load_token<T>(
&mut self,
predicate: fn(usize, &Token) -> Option<T>,
) -> Result<Option<T>, Box<LatexError>> {
if self.lexer_is_eoi {
return Ok(None);
}
let starting_len = self.queue.len();
let mut non_skipped_offset = 0usize;
loop {
let tok = self.lexer.next_token()?;
let tok = self.resolve_lexed(tok);
let result = predicate(starting_len + non_skipped_offset, tok.token());
let is_eoi = matches!(tok.token(), Token::Eoi);
self.queue.push_back(tok);
if let Some(result) = result {
return Ok(Some(result));
}
non_skipped_offset += 1;
if is_eoi {
self.lexer_is_eoi = true;
return Ok(None);
}
}
}
fn find_next_non_whitespace(&self) -> Option<usize> {
self.queue
.iter()
.position(|tokspan| !matches!(tokspan.token(), Token::Whitespace))
}
fn ensure_next_non_whitespace(&mut self) -> Result<(), Box<LatexError>> {
let pos = 'pos_calc: {
if !self.queue.is_empty()
&& let Some(pos) = self.find_next_non_whitespace()
{
break 'pos_calc pos;
}
self.load_token_skip_whitespace()?
};
self.next_non_whitespace = pos;
Ok(())
}
#[inline]
pub(super) fn peek(&self) -> &TokSpan {
if let Some(tok) = self.queue.get(self.next_non_whitespace) {
tok.tokspan()
} else {
debug_assert!(self.lexer_is_eoi, "peek called without ensure");
EOI_TOK.tokspan()
}
}
#[inline]
pub(super) fn peek_any_token(&self) -> &TokSpan {
self.peek_any_keeping_name().tokspan()
}
#[inline]
fn peek_any_keeping_name(&self) -> &QueuedTok<'arena> {
if let Some(tok) = self.queue.front() {
tok
} else {
debug_assert!(self.lexer_is_eoi, "peek called without ensure");
&EOI_TOK
}
}
fn find_or_load_after_next<T>(
&mut self,
predicate: fn(usize, &Token) -> Option<T>,
) -> Result<Option<T>, Box<LatexError>> {
let result = {
let start = self.next_non_whitespace;
if start < self.queue.len() {
let mut range = self.queue.range(start..);
range.next(); range
.enumerate()
.find_map(|(idx, ts)| predicate(start + 1 + idx, ts.token()))
} else {
debug_assert!(
self.lexer_is_eoi,
"find_or_load_after_next called without ensure"
);
return Ok(None);
}
};
if let Some(result) = result {
Ok(Some(result))
} else {
self.load_token(predicate)
}
}
pub(super) fn peek_second(&mut self) -> Result<&TokSpan, Box<LatexError>> {
if let Some(tok) = self
.find_or_load_after_next(is_not_whitespace)?
.and_then(|idx| self.queue.get(idx))
{
Ok(tok.tokspan())
} else {
debug_assert!(self.lexer_is_eoi, "peek_second called without ensure");
Ok(EOI_TOK.tokspan())
}
}
pub(super) fn peek_class_token(&mut self) -> Result<(usize, Class), Box<LatexError>> {
if let Some(class) = self.peek().token().class() {
Ok((self.next_non_whitespace, class))
} else if let Some(class) = self.find_or_load_after_next(has_class)? {
Ok(class)
} else {
debug_assert!(self.lexer_is_eoi, "peek_class_token called without ensure");
Ok((self.queue.len(), Class::End))
}
}
fn reject_unknown_command(&self, tokspan: &TokSpan) -> Result<(), Box<LatexError>> {
if let Token::UnresolvedCommand(name) = *tokspan.token()
&& !self.stores.parser_cfg.ignore_unknown_commands
{
return Err(Box::new(LatexError(
tokspan.span().into(),
LatexErrKind::UnknownCommand(KString::from_ref(self.cmd_names.get(name))),
)));
}
Ok(())
}
pub(super) fn next(&mut self) -> Result<TokSpan, Box<LatexError>> {
Ok(self.next_keeping_name()?.into_tokspan())
}
pub(super) fn next_keeping_name(&mut self) -> Result<QueuedTok<'arena>, Box<LatexError>> {
let ret = self.next_allowing_unresolved_command()?;
self.reject_unknown_command(ret.tokspan())?;
Ok(ret)
}
pub(super) fn next_allowing_unresolved_command(
&mut self,
) -> Result<QueuedTok<'arena>, Box<LatexError>> {
for _ in 0..self.next_non_whitespace {
let _ = self.queue.pop_front();
}
if let Some(ret) = self.queue.pop_front() {
self.ensure_next_non_whitespace()?;
let ret = ret.unwrap_math();
debug_assert!(!matches!(
ret.token(),
Token::Whitespace | Token::MathOrTextMode(_, _)
));
Ok(ret)
} else {
debug_assert!(self.lexer_is_eoi, "next called without ensure");
Ok(EOI_TOK)
}
}
pub(super) fn next_any_token(&mut self) -> Result<TokSpan, Box<LatexError>> {
Ok(self.next_any_token_keeping_name()?.into_tokspan())
}
fn next_any_token_keeping_name(&mut self) -> Result<QueuedTok<'arena>, Box<LatexError>> {
let ret = self.next_any_token_allowing_unknown_command()?;
self.reject_unknown_command(ret.tokspan())?;
Ok(ret)
}
pub(super) fn next_any_token_allowing_unknown_command(
&mut self,
) -> Result<QueuedTok<'arena>, Box<LatexError>> {
if let Some(ret) = self.queue.pop_front() {
if let Some(new_pos) = self.next_non_whitespace.checked_sub(1) {
self.next_non_whitespace = new_pos;
} else {
self.ensure_next_non_whitespace()?;
}
Ok(ret)
} else {
debug_assert!(
self.lexer_is_eoi,
"next_with_whitespace called without ensure"
);
Ok(EOI_TOK)
}
}
pub(super) fn queue_in_front(&mut self, tokens: &[impl Into<QueuedTok<'arena>> + Copy]) {
self.queue.reserve(tokens.len());
for tok in tokens.iter().rev() {
self.queue.push_front((*tok).into());
}
self.update_next_non_whitespace();
}
fn update_next_non_whitespace(&mut self) {
if let Some(pos) = self.find_next_non_whitespace() {
self.next_non_whitespace = pos;
} else {
debug_assert!(self.lexer_is_eoi, "queued in front without ensure");
self.next_non_whitespace = self.queue.len();
}
}
pub(super) fn queue_body_substituting(
&mut self,
tokens: &[Token],
args: &CmdArgs<'arena>,
span: Span,
) {
if tokens.is_empty() {
self.queue_empty_body(span);
return;
}
self.queue.reserve(tokens.len());
for tok in tokens.iter().rev() {
match *tok {
Token::CustomCmdArg(arg_num) => {
push_arg_front(&mut self.queue, args.get(arg_num), span);
}
tok => self.queue.push_front(tok.into()),
}
}
self.update_next_non_whitespace();
}
#[must_use]
pub(super) fn queue_stored_body_substituting(
&mut self,
source: CmdSource,
start: usize,
end: usize,
args: &CmdArgs<'arena>,
span: Span,
) -> bool {
let Some(body) = self.stores.get_body(source, start, end) else {
return false;
};
if body.is_empty() {
self.queue_empty_body(span);
return true;
}
self.queue.reserve(body.len());
for recorded in body.iter().rev() {
match recorded {
RecordedToken::Token(Token::CustomCmdArg(arg_num)) => {
push_arg_front(&mut self.queue, args.get(*arg_num), span);
}
RecordedToken::Token(tok) => self.queue.push_front((*tok).into()),
RecordedToken::CommandName(name) => {
let queued = match self.stores.resolve_command(name) {
Some(tok) => tok.into(),
None => {
let name = self.cmd_names.intern(name);
TokSpan::new(Token::UnresolvedCommand(name), span).into()
}
};
self.queue.push_front(queued);
}
}
}
self.update_next_non_whitespace();
true
}
fn queue_empty_body(&mut self, span: Span) {
self.queue
.push_front(TokSpan::new(Token::Relax, span).into());
self.update_next_non_whitespace();
}
pub(super) fn queue_arg_in_front(&mut self, arg: &[QueuedTok<'arena>], span: Span) {
push_arg_front(&mut self.queue, arg, span);
self.update_next_non_whitespace();
}
pub(super) fn resolve_buffered_unknown_commands(&mut self) {
for queued in &mut self.queue {
if let Token::UnresolvedCommand(name) = *queued.token()
&& let Some(tok) = self.stores.resolve_command(self.cmd_names.get(name))
{
*queued = queued.with_token(tok);
}
}
}
pub(super) fn define(
&mut self,
name: &str,
num_args: u8,
body: &[RecordedToken],
first_class: Option<Class>,
replace: bool,
is_global: bool,
) -> bool {
let is_global = is_global || self.stores.parser_cfg.global_group;
let source = if is_global {
CmdSource::Document
} else {
CmdSource::Local
};
let stores = &mut self.stores;
if is_global {
let local = &stores.local_cmds;
let document = &mut stores.global_state.custom_cmds;
if replace {
document.insert_or_replace_and_copy_local(local, name, num_args, body, first_class);
} else if !document.insert_and_copy_local(local, name, num_args, body, first_class) {
return false;
}
stores.local_cmds.remove(name);
} else if replace {
stores
.local_cmds
.insert_or_replace(name, num_args, body, first_class);
} else if !stores.local_cmds.insert(name, num_args, body, first_class) {
return false;
}
let store = match source {
CmdSource::Document => &self.stores.global_state.custom_cmds,
_ => &self.stores.local_cmds,
};
if replace {
if let Some(tok) = store.get(name, source) {
self.resolve_buffered_redefined_command(name, tok);
}
} else {
self.resolve_buffered_unknown_commands();
}
true
}
pub(super) fn resolve_buffered_redefined_command(&mut self, name: &str, tok: Token) {
for queued in &mut self.queue {
if queued.name() == Some(name) {
*queued = queued.with_token(tok);
}
}
}
pub(super) fn record_macro_body(
&mut self,
tokens: &mut Vec<QueuedTok<'arena>>,
) -> Result<(), Box<LatexError>> {
core::debug_assert_matches!(self.peek().token(), Token::GroupBegin);
self.next()?; let mut nesting_level = 0usize;
loop {
let tokloc = *self.peek_any_keeping_name();
match tokloc.token() {
Token::GroupBegin => {
nesting_level += 1;
}
Token::GroupEnd => {
let Some(new_level) = nesting_level.checked_sub(1) else {
return Ok(());
};
nesting_level = new_level;
}
Token::Eoi => {
return Err(Box::new(LatexError(
tokloc.span().into(),
LatexErrKind::UnclosedGroup(EndToken::GroupClose),
)));
}
_ => {}
}
self.next_any_token_allowing_unknown_command()?;
tokens.push(tokloc);
}
}
pub(super) fn read_definition_name(
&mut self,
arena: &'arena Arena,
) -> Result<&'arena str, Box<LatexError>> {
let name_tokspan = self.next_allowing_unresolved_command()?;
match *name_tokspan.token() {
Token::UnresolvedCommand(name) => {
Ok(arena.alloc_str(self.cmd_names.get(name)))
}
_ => name_tokspan.name().ok_or_else(|| {
Box::new(LatexError(
name_tokspan.span().into(),
LatexErrKind::ExpectedCommandName,
))
}),
}
}
pub(super) fn map_recorded_tokens(
&self,
body_tokspans: Vec<QueuedTok<'arena>>,
num_args: u8,
) -> Result<(Vec<RecordedToken>, Option<Class>), Box<LatexError>> {
let mut first_class: Option<Class> = None;
let body = body_tokspans
.into_iter()
.map(|queued| {
let (tok, span) = queued.into_tokspan().into_parts();
match tok {
Token::CustomCmdArgInput(arg_num) => {
if arg_num >= num_args {
return Err(Box::new(LatexError(
span.into(),
LatexErrKind::ParameterNumberOutOfRange {
actual: arg_num + 1,
n: num_args,
},
)));
}
Ok(RecordedToken::Token(Token::CustomCmdArg(arg_num)))
}
Token::NewCommand(_) | Token::Let | Token::Def(_) | Token::Global => {
Err(Box::new(LatexError(
span.into(),
LatexErrKind::CannotBeUsedAsArgument,
)))
}
tok => {
if first_class.is_none() {
first_class = tok.class();
}
match tok {
Token::UnresolvedCommand(name) => Ok(RecordedToken::CommandName(
KString::from_ref(self.cmd_names.get(name)),
)),
tok => match queued.name() {
Some(name) => {
Ok(RecordedToken::CommandName(KString::from_ref(name)))
}
None => Ok(RecordedToken::Token(tok)),
},
}
}
}
})
.collect::<Result<Vec<RecordedToken>, _>>()?;
Ok((body, first_class))
}
pub(super) fn record_group<T: From<QueuedTok<'arena>>>(
&mut self,
tokens: &mut Vec<T>,
preserve_all: bool,
) -> Result<usize, Box<LatexError>> {
let mut nesting_level = 0usize;
let end = loop {
let tokloc = if preserve_all {
self.next_any_token_keeping_name()
} else {
self.next_keeping_name()
};
let tokloc = tokloc?;
match tokloc.token() {
Token::GroupBegin => {
nesting_level += 1;
}
Token::GroupEnd => {
let Some(new_level) = nesting_level.checked_sub(1) else {
break tokloc.span().end();
};
nesting_level = new_level;
}
Token::Eoi => {
return Err(Box::new(LatexError(
tokloc.span().into(),
LatexErrKind::UnclosedGroup(EndToken::GroupClose),
)));
}
_ => {}
}
tokens.push(tokloc.into());
};
Ok(end)
}
pub fn read_argument(&mut self, preserve_all: bool) -> Result<MacroArgument, Box<LatexError>> {
let first = if preserve_all {
let tok = *self.peek();
self.next()?;
tok
} else {
self.next()?
};
if matches!(first.token(), Token::GroupBegin) {
let mut tokens = Vec::new();
let end_loc = self.record_group(&mut tokens, preserve_all)?;
Ok(MacroArgument::Group(tokens, first.span().start()..end_loc))
} else {
Ok(MacroArgument::Token(first))
}
}
pub(super) fn get_token_by_index(&self, idx: usize) -> Option<&TokSpan> {
self.queue.get(idx).map(QueuedTok::tokspan)
}
}
fn push_arg_front<'arena>(
queue: &mut VecDeque<QueuedTok<'arena>>,
arg: &[QueuedTok<'arena>],
span: Span,
) {
if arg.is_empty() {
queue.reserve(2);
queue.push_front(TokSpan::new(Token::GroupEnd, span).into());
queue.push_front(TokSpan::new(Token::GroupBegin, span).into());
} else {
queue.reserve(arg.len());
for queued in arg.iter().rev() {
queue.push_front(*queued);
}
}
}
fn is_not_whitespace(idx: usize, tok: &Token) -> Option<usize> {
(!matches!(tok, Token::Whitespace)).then_some(idx)
}
fn has_class(idx: usize, tok: &Token) -> Option<(usize, Class)> {
tok.class().map(|class| (idx, class))
}
#[derive(Debug, Default)]
pub(super) struct CmdArgs<'source> {
tokens: Vec<QueuedTok<'source>>,
offsets: [usize; 9],
}
impl<'source> CmdArgs<'source> {
pub(super) fn get(&self, arg_num: u8) -> &[QueuedTok<'source>] {
let start = self
.offsets
.get(arg_num.wrapping_sub(1) as usize)
.copied()
.unwrap_or(0);
let end = self
.offsets
.get(arg_num as usize)
.copied()
.unwrap_or(self.tokens.len());
self.tokens.get(start..end).unwrap_or(&[])
}
pub(super) fn clear(&mut self) {
self.tokens.clear();
self.offsets = [0; 9];
}
pub(super) fn push(&mut self, queued: QueuedTok<'source>) {
self.tokens.push(queued);
}
pub(super) fn finish_arg(&mut self, arg_num: u8) {
if let Some(offset) = self.offsets.get_mut(arg_num as usize) {
*offset = self.tokens.len();
}
}
pub(super) fn buffer(&mut self) -> &mut Vec<QueuedTok<'source>> {
&mut self.tokens
}
}
pub enum MacroArgument {
Token(TokSpan),
Group(Vec<TokSpan>, Range<usize>),
}
impl MacroArgument {
pub fn into_one_or_none(self) -> Result<OneOrNone, Box<LatexError>> {
match self {
MacroArgument::Token(tok) => Ok(OneOrNone::One(tok)),
MacroArgument::Group(tokens, span) => {
if tokens.is_empty() {
Ok(OneOrNone::None(span))
} else if let Ok([tokspan]) = <[TokSpan; 1]>::try_from(tokens) {
Ok(OneOrNone::One(tokspan))
} else {
Err(Box::new(LatexError(
span,
LatexErrKind::ExpectedAtMostOneToken,
)))
}
}
}
}
pub fn into_one(self) -> Result<TokSpan, Box<LatexError>> {
match self {
MacroArgument::Token(tok) => Ok(tok),
MacroArgument::Group(tokens, span) => {
if let Ok([tokspan]) = <[TokSpan; 1]>::try_from(tokens) {
Ok(tokspan)
} else {
Err(Box::new(LatexError(
span,
LatexErrKind::ExpectedExactlyOneToken,
)))
}
}
}
}
}
pub enum OneOrNone {
One(TokSpan),
None(Range<usize>),
}
impl From<OneOrNone> for Option<TokSpan> {
fn from(value: OneOrNone) -> Self {
match value {
OneOrNone::One(tok) => Some(tok),
OneOrNone::None(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use insta::assert_snapshot;
use super::*;
#[test]
fn test_record_group() {
let problems = [
("simple_group", r"{x+y}"),
("group_followed", r"{x+y} b"),
("nested_group", r"{x + {y - z}} c"),
("unclosed_group", r"{x + y"),
("unclosed_nested_group", r"{x + {y + z}"),
("too_many_closes", r"{x + y} + z}"),
("empty_group", r"{} d"),
("group_with_begin", r"{\begin{matrix}}"),
("early_error", r"{x + \unknowncmd + y}"),
];
let parser_cfg = crate::ParserConfig::default();
let mut state = crate::GlobalState::default();
for (name, problem) in problems.into_iter() {
let lexer = Lexer::new(problem);
let mut manager = TokenQueue::new(lexer, &parser_cfg, &mut state)
.expect("Failed to create TokenManager");
manager.load_token_skip_whitespace().unwrap();
manager.load_token_skip_whitespace().unwrap();
std::assert_matches!(manager.next().unwrap().token(), Token::GroupBegin);
let mut tokens: Vec<TokSpan> = Vec::new();
let tokens = match manager.record_group(&mut tokens, true) {
Ok(_) => {
let mut token_str = String::new();
for tokloc in tokens {
let (tok, span) = tokloc.into_parts();
writeln!(token_str, "{}..{}: {:?}", span.start(), span.end(), tok).unwrap();
}
token_str
}
Err(error) => {
let report = error.to_report("<input>", false);
let mut buf = Vec::new();
report
.write(("<input>", ariadne::Source::from(problem)), &mut buf)
.expect("failed to write report");
String::from_utf8(buf).expect("report should be valid UTF-8")
}
};
assert_snapshot!(name, &tokens, problem);
}
}
#[test]
fn test_get_whitespace_tokens() {
let input = r"\text{ x + y }";
let parser_cfg = crate::ParserConfig::default();
let mut state = crate::GlobalState::default();
let lexer = Lexer::new(input);
let mut manager =
TokenQueue::new(lexer, &parser_cfg, &mut state).expect("Failed to create TokenManager");
let mut token_str = String::new();
loop {
let (tok, span) = manager.next_any_token().unwrap().into_parts();
if matches!(tok, Token::Eoi) {
break;
}
writeln!(token_str, "{}..{}: {:?}", span.start(), span.end(), tok).unwrap();
}
assert_snapshot!("next_with_whitespace", &token_str, input);
}
#[test]
fn test_find_or_load_after_next() {
let input = r"x y z";
let parser_cfg = crate::ParserConfig::default();
let mut state = crate::GlobalState::default();
let lexer = Lexer::new(input);
let mut queue =
TokenQueue::new(lexer, &parser_cfg, &mut state).expect("Failed to create TokenManager");
queue.next().unwrap(); assert_eq!(queue.next_non_whitespace, 1);
assert_eq!(queue.queue.len(), 2);
std::assert_matches!(queue.queue[0].token(), Token::Whitespace);
assert!(
matches!(queue.peek().token(), Token::Letter(c, _) if c.try_as_char() == Some('y'))
);
let tok_idx = queue.find_or_load_after_next(is_not_whitespace).unwrap();
std::assert_matches!(tok_idx, Some(3));
assert_eq!(queue.queue.len(), 4);
std::assert_matches!(queue.queue[0].token(), Token::Whitespace);
std::assert_matches!(queue.queue[2].token(), Token::Whitespace);
assert!(
matches!(queue.queue[3].token(), Token::Letter(c, _) if c.try_as_char() == Some('z'))
);
let tok_idx = queue.find_or_load_after_next(is_not_whitespace).unwrap();
std::assert_matches!(tok_idx, Some(3));
assert_eq!(queue.queue.len(), 4);
std::assert_matches!(queue.queue[0].token(), Token::Whitespace);
std::assert_matches!(queue.queue[2].token(), Token::Whitespace);
assert!(
matches!(queue.queue[3].token(), Token::Letter(c, _)if c.try_as_char() == Some('z'))
);
}
#[test]
fn text_read_argument() {
let problems = [
("hyphen", r"-xy", true),
("hyphen_math_mode", r"-xy", false),
("consecutive_whitespace", r"{x y} z", true),
("leading_whitespace", r" {x y} z", true),
("consecutive_whitespace_skip", r"{x y} z", false),
];
let parser_cfg = crate::ParserConfig::default();
for (name, problem, preserve_all) in problems.into_iter() {
let mut state = crate::GlobalState::default();
let lexer = Lexer::new(problem);
let mut manager = TokenQueue::new(lexer, &parser_cfg, &mut state)
.expect("Failed to create TokenManager");
let tokens = match manager.read_argument(preserve_all) {
Ok(MacroArgument::Group(tokens, _)) => {
let mut token_str = String::new();
for tokloc in tokens {
let (tok, span) = tokloc.into_parts();
writeln!(token_str, "{}..{}: {:?}", span.start(), span.end(), tok).unwrap();
}
token_str
}
Ok(MacroArgument::Token(tok)) => {
let (tok, span) = tok.into_parts();
format!("{}..{}: {:?}\n", span.start(), span.end(), tok)
}
Err(error) => {
let report = error.to_report("<input>", false);
let mut buf = Vec::new();
report
.write(("<input>", ariadne::Source::from(problem)), &mut buf)
.expect("failed to write report");
String::from_utf8(buf).expect("report should be valid UTF-8")
}
};
assert_snapshot!(name, &tokens, problem);
}
}
}