use crate::internal_prelude::*;
pub(crate) enum NextItem {
CommandInvocation(CommandInvocation),
VariableSubstitution(VariableSubstitution),
Group(Group),
Leaf(TokenTree),
EndOfStream,
}
pub(crate) fn parse_next_item(tokens: &mut Tokens) -> Result<NextItem> {
Ok(match tokens.next() {
Some(TokenTree::Group(group)) => {
if let Some(command_invocation) = parse_command_invocation(&group)? {
NextItem::CommandInvocation(command_invocation)
} else {
NextItem::Group(group)
}
}
Some(TokenTree::Punct(punct)) => {
if let Some(variable_substitution) = parse_only_if_variable_substitution(&punct, tokens)
{
NextItem::VariableSubstitution(variable_substitution)
} else {
NextItem::Leaf(TokenTree::Punct(punct))
}
}
Some(leaf) => NextItem::Leaf(leaf),
None => NextItem::EndOfStream,
})
}
pub(crate) fn parse_variable_set(tokens: &mut Tokens) -> Option<Ident> {
let variable_name = parse_variable(tokens)?;
tokens.next_as_punct_matching('=')?;
Some(variable_name)
}
pub(crate) fn parse_variable(tokens: &mut Tokens) -> Option<Ident> {
tokens.next_as_punct_matching('#')?;
tokens.next_as_ident()
}
fn parse_command_invocation(group: &Group) -> Result<Option<CommandInvocation>> {
fn consume_command_start(group: &Group) -> Option<(Ident, Tokens)> {
if group.delimiter() != Delimiter::Bracket {
return None;
}
let mut tokens = Tokens::new(group.stream());
tokens.next_as_punct_matching('!')?;
let ident = tokens.next_as_ident()?;
Some((ident, tokens))
}
fn consume_command_end(command_ident: &Ident, tokens: &mut Tokens) -> Option<CommandKind> {
let command_kind = CommandKind::attempt_parse(command_ident)?;
tokens.next_as_punct_matching('!')?;
Some(command_kind)
}
let (command_ident, mut remaining_tokens) = match consume_command_start(group) {
Some(command_start) => command_start,
None => return Ok(None),
};
match consume_command_end(&command_ident, &mut remaining_tokens) {
Some(command_kind) => Ok(Some(CommandInvocation::new(command_kind, group, remaining_tokens))),
None => Err(Error::new(
command_ident.span(),
format!(
"Expected `[!<command>! ..]`, for <command> one of: {}.\nIf this wasn't intended to be a preinterpret command, you can work around this with [!raw! [!{} ... ]]",
CommandKind::list_all(),
command_ident,
),
)),
}
}
fn parse_only_if_variable_substitution(
punct: &Punct,
tokens: &mut Tokens,
) -> Option<VariableSubstitution> {
if punct.as_char() != '#' {
return None;
}
match tokens.peek() {
Some(TokenTree::Ident(_)) => {}
_ => return None,
}
match tokens.next() {
Some(TokenTree::Ident(variable_name)) => {
Some(VariableSubstitution::new(punct.clone(), variable_name))
}
_ => unreachable!("We just peeked a token of this type"),
}
}