#![allow(unreachable_code)]
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::atomic::AtomicU32;
#[allow(clippy::wildcard_imports)]
use unsynn::*;
#[allow(clippy::wildcard_imports)]
use crate::xtoken::*;
pub struct XMacro<'a> {
pub xtokens: Vec<XToken>,
pub definitions: Definitions<'a>,
}
impl Parser for XMacro<'_> {
#[allow(clippy::too_many_lines)]
fn parser(input: &mut TokenIter) -> Result<Self> {
let mut scope_inner = Self {
xtokens: Vec::new(),
definitions: Definitions::new(None),
};
scope_inner.xtokens = parse_scope_content(input, &mut scope_inner, false)?;
Ok(scope_inner)
}
}
pub struct XMacroItem<'a>(pub XMacro<'a>);
impl Parser for XMacroItem<'_> {
#[allow(clippy::too_many_lines)]
fn parser(input: &mut TokenIter) -> Result<Self> {
let mut scope_inner = Self(XMacro {
xtokens: Vec::new(),
definitions: Definitions::new(None),
});
scope_inner.0.xtokens = parse_scope_content(input, &mut scope_inner.0, true)?;
Ok(scope_inner)
}
}
#[allow(clippy::too_many_lines)]
#[allow(clippy::items_after_statements)]
fn parse_scope_content(
input: &mut TokenIter,
scope_inner: &mut XMacro,
break_at_item: bool,
) -> Result<Vec<XToken>> {
let mut xtokens = Vec::new();
loop {
match XToken::parse(input) {
Ok(XToken::Group(group)) => {
xtokens.push(XToken::XGroup(GroupContaining {
delimiter: group.delimiter(),
content: parse_scope_content(
&mut group.stream().into_token_iter(),
scope_inner,
false,
)?,
}));
if break_at_item && group.delimiter() == Delimiter::Brace {
break;
}
}
Ok(XToken::Scope(_, braced)) => {
let mut child_inner = XMacro {
xtokens: Vec::new(),
definitions: Definitions::new(Some(&scope_inner.definitions)),
};
child_inner.xtokens = parse_scope_content(
&mut braced.0.stream().to_token_iter(),
&mut child_inner,
false,
)?;
let mut output = TokenStream::new();
for token in child_inner.expand() {
token.to_tokens(&mut output);
}
xtokens.push(XToken::TokenStream(output));
}
Ok(XToken::Definition(_, def)) => {
let mut start_index = scope_inner.definitions.definitions.len();
let mut inplace_defs: Vec<bool> = def
.content
.names
.iter()
.map(|n| n.inplace.is_some())
.collect();
if inplace_defs.is_empty() {
inplace_defs.push(true);
}
scope_inner
.definitions
.extend(def.content, input.counter())?;
for inplace in inplace_defs {
if inplace {
scope_inner.definitions.definitions[start_index].do_expand = true;
xtokens.push(XToken::IndexedSubstitution(Dollar::new(), start_index));
}
start_index += 1;
}
}
Ok(XToken::DefinitionError(_, def)) => {
return Error::other(
input,
format!("definition/table syntax: {}", def.tokens_to_string()),
);
}
Ok(XToken::NamedSubstitution(d, i)) => {
let index = scope_inner.definitions.copy_lookup(i, input.counter())?;
scope_inner.definitions.definitions[index].do_expand = true;
xtokens.push(XToken::IndexedSubstitution(d, index));
}
Ok(XToken::IndexOfNamed(op, i)) => {
let index = scope_inner.definitions.copy_lookup(i, input.counter())?;
scope_inner.definitions.definitions[index].do_expand = true;
xtokens.push(XToken::IndexOfIndexed(op, index));
}
Ok(t @ XToken::IndexOfIndexed(_, index)) => {
scope_inner.definitions.definitions[index].do_expand = true;
xtokens.push(t);
}
Ok(XToken::LengthOfNamed(op, i)) => {
let index = scope_inner.definitions.copy_lookup(i, input.counter())?;
xtokens.push(XToken::LengthOfIndexed(op, index));
}
Ok(XToken::Directive(_, directive)) => {
let id = directive.as_str();
return Error::other(
input,
format!(
"unknown directive {:?}: at {:?}",
id,
directive.span().start(),
),
);
}
Ok(XToken::BreakSemicolon(_)) => {
xtokens.push(XToken::TokenTree(PunctAlone::<';'>::new().into()));
if break_at_item {
break;
}
}
Ok(XToken::BreakPunct(p, _)) => {
xtokens.push(XToken::TokenTree(TokenTree::Punct(Punct::new(
p.as_char(),
Spacing::Alone,
))));
}
Ok(XToken::IndexedSubstitution(op, index)) => {
scope_inner.definitions.definitions[index].do_expand = true;
xtokens.push(XToken::IndexedSubstitution(op, index));
}
Ok(XToken::ConcatIdent(op, ident, dollar, def)) => {
let index = def.fold2(Ok, |name| {
let index = scope_inner.definitions.copy_lookup(name, input.counter())?;
scope_inner.definitions.definitions[index].do_expand = true;
Ok(index)
})?;
xtokens.push(XToken::ConcatIdent(op, ident, dollar, Either::First(index)));
}
Ok(
t @ (XToken::TokenTree(_) | XToken::LengthOfIndexed(..) | XToken::EscapedDollar(_)),
) => {
xtokens.push(t);
}
Ok(XToken::EndOfStream(_)) => break,
Err(e) => return Err(e),
Ok(XToken::XGroup(_) | XToken::TokenStream(_)) => unreachable!(),
}
}
Ok(xtokens)
}
#[test]
fn smoke_empty() {
let mut tokens = "".into_token_iter();
let xscope = XMacro::parse_all(&mut tokens).expect("XMacro");
assert!(xscope.xtokens.is_empty());
}
#[test]
fn smoke_some_tokens() {
let mut tokens = r#"this (is a) "test""#.into_token_iter();
let xscope = XMacro::parse_all(&mut tokens).expect("XMacro");
assert!((xscope.xtokens.len()) == (3));
}
impl ToTokens for XMacro<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
for token in &self.xtokens {
token.to_tokens(tokens);
}
}
}
#[derive(Debug, Clone)]
pub struct Definition<'a> {
pub def: Cow<'a, [TokenStream]>,
pub do_expand: bool,
pub group_id: u32,
}
#[derive(Debug)]
#[allow(clippy::struct_field_names)]
pub struct Definitions<'a> {
parent: Option<&'a Definitions<'a>>,
pub definitions: Vec<Definition<'a>>,
names: HashMap<CachedIdent, usize>,
}
impl<'a> Definitions<'a> {
fn new(parent: Option<&'a Self>) -> Self {
Self {
parent,
definitions: Vec::new(),
names: HashMap::new(),
}
}
}
impl Definitions<'_> {
fn extend(&mut self, definitions: DefinitionContent, pos: usize) -> Result<()> {
let DefinitionContent { names, definitions } = definitions;
let index_start = self.definitions.len();
let group_id = new_group_id();
self.definitions
.extend(definitions.into_iter().map(|def| Definition {
def,
do_expand: false,
group_id,
}));
for (index, name) in names.into_iter().enumerate() {
if self
.names
.insert(name.name.clone(), index_start + index)
.is_some()
{
return Error::other(
pos,
format!(
"name {:?} already defined: at {:?}",
&name,
&name.name.span().start(),
),
);
}
}
Ok(())
}
fn copy_lookup(&mut self, name: CachedIdent, pos: usize) -> Result<usize> {
if let Some(index) = self.names.get(&name) {
Ok(*index)
} else {
let mut parent = self.parent;
while let Some(p) = parent {
if let Some(index) = p.names.get(&name) {
self.definitions.push(Definition {
def: Cow::Borrowed(&p.definitions[*index].def),
do_expand: p.definitions[*index].do_expand,
group_id: p.definitions[*index].group_id,
});
let index = self.definitions.len() - 1;
self.names.insert(name, index);
return Ok(index);
}
parent = p.parent;
}
Error::other(
pos,
format!(
"'${}' not defined: at {:?}",
name.as_str(),
&name.span().start(),
),
)
}
}
}
fn new_group_id() -> u32 {
static GROUP_CNT: AtomicU32 = AtomicU32::new(0);
GROUP_CNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}