use proc_macro2::TokenStream;
use quote::ToTokens;
use std::ops::Deref;
use syn::{parse, parse::Parse, parse2, Error};
use crate::{Expand, Lint};
pub struct Collector {
err_count: usize,
output: TokenStream,
}
impl Collector {
pub fn new() -> Self {
Collector {
err_count: 0,
output: TokenStream::new(),
}
}
pub fn error(&mut self, e: Error) {
let error: TokenStream = e.to_compile_error();
self.output.extend(error);
self.err_count += 1;
}
pub fn has_errors(&self) -> bool {
self.err_count != 0
}
pub fn finish(self) -> TokenStream {
self.output
}
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
enum Data<'a, T> {
Owned(T),
Borrowed(&'a T),
}
impl<T> Deref for Data<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Data::Owned(data) => &data,
Data::Borrowed(data) => data,
}
}
}
pub struct Context<'a, T> {
collector: &'a mut Collector,
data: Option<Data<'a, T>>,
}
impl<'a, T> Context<'a, T> {
pub fn new(collector: &'a mut Collector, data: T) -> Self {
Context {
collector,
data: Some(Data::Owned(data)),
}
}
pub fn new_by_ref(collector: &'a mut Collector, data: &'a T) -> Self {
Context {
collector,
data: Some(Data::Borrowed(data)),
}
}
pub fn new_empty(collector: &'a mut Collector) -> Self {
Context {
collector,
data: None,
}
}
pub fn new_parse(collector: &'a mut Collector, data: proc_macro::TokenStream) -> Self
where
T: Parse,
{
match parse::<T>(data) {
Ok(data) => Self::new(collector, data),
Err(e) => {
collector.error(e);
Self {
collector,
data: None,
}
}
}
}
pub fn new_parse2(collector: &'a mut Collector, data: TokenStream) -> Self
where
T: Parse,
{
match parse2::<T>(data) {
Ok(data) => Self::new(collector, data),
Err(e) => {
collector.error(e);
Self {
collector,
data: None,
}
}
}
}
pub fn lint<L: Lint<T>>(&mut self, lint: &L) -> bool {
if let Some(data) = self.data.take() {
let start = self.collector.err_count;
lint.lint(&*data, &mut self.collector);
self.data = Some(data);
start == self.collector.err_count
} else {
false
}
}
pub fn expand(&mut self, expand: &impl Expand<T>) {
if let Some(res) = self.capture(expand) {
let tts: TokenStream = res.to_token_stream();
self.collector.output.extend(tts);
}
}
pub fn capture<E: Expand<T>>(&mut self, expand: &E) -> Option<E::Output> {
if self.collector.has_errors() {
return None;
}
if let Some(data) = self.data.as_ref() {
expand.expand(&*data, &mut self.collector)
} else {
None
}
}
}