smd_macro 0.0.7

A crate for the smd! macro
use crate::types::*;
use proc_macro2::{
  Delimiter,
  Group,
  Literal,
  Spacing,
  TokenStream,
};
use quote::{
  quote,
  ToTokens,
};
use std::iter;

pub type TtsIResult<'a, T> = nom::IResult<TokenTreeSlice<'a>, T>;
pub type StringResult<'a> = TtsIResult<'a, String>;

pub fn match_string_from_hashmap<'a>(
  input: TokenTreeSlice<'a>,
  map: &std::collections::HashMap<String, String>,
) -> TtsIResult<'a, String> {
  let get_err = || {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  };

  match input.split_first() {
    Some((first, rest)) => match first {
      TokenTree::Ident(ref ident) => {
        let s = ident.to_string();
        let val_opt = map.get(&s);
        match val_opt {
          Some(val) => Ok((rest, val.to_string())),
          None => get_err(),
        }
      },
      _ => get_err(),
    },
    None => get_err(),
  }
}

pub fn match_punct(
  input: TokenTreeSlice,
  c_opt: Option<char>,
  spacing_opt: Option<Spacing>,
  excluded_chars: Vec<char>,
) -> StringResult {
  let get_err = || {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  };
  let filler_spaces = get_filler_spaces(input);

  match input.split_first() {
    Some((first, rest)) => match first {
      TokenTree::Punct(ref punct) => {
        let wrong_char = c_opt.map(|c| punct.as_char() != c).unwrap_or(false);
        let wrong_spacing = spacing_opt
          .map(|spacing| punct.spacing() != spacing)
          .unwrap_or(false);
        let contains_excluded_char = excluded_chars.contains(&punct.as_char());

        if wrong_char || wrong_spacing || contains_excluded_char {
          get_err()
        } else {
          Ok((rest, format!("{}{}", punct.as_char(), filler_spaces)))
        }
      },
      _ => get_err(),
    },
    None => get_err(),
  }
}

pub fn match_ident(
  input: TokenTreeSlice,
  sym_opt: Option<String>,
  include_filler: bool,
) -> StringResult {
  let get_err = || {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  };

  let filler_spaces = if include_filler {
    get_filler_spaces(input)
  } else {
    "".into()
  };
  match input.split_first() {
    Some((first, rest)) => match first {
      TokenTree::Ident(ref ident) => {
        let get_success = || Ok((rest, format!("{}{}", ident, filler_spaces)));
        match sym_opt {
          Some(s) => {
            if s == format!("{}", ident) {
              get_success()
            } else {
              get_err()
            }
          },
          None => get_success(),
        }
      },
      _ => get_err(),
    },
    None => get_err(),
  }
}

pub type GroupResult<'a> = TtsIResult<'a, Group>;

pub fn match_group(input: TokenTreeSlice, delimiter_opt: Option<Delimiter>) -> GroupResult {
  let get_err = || {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  };

  match input.split_first() {
    Some((first, rest)) => match first {
      TokenTree::Group(ref group) => {
        let get_success = || Ok((rest, group.clone()));
        match delimiter_opt {
          Some(delimiter) => {
            if group.delimiter() == delimiter {
              get_success()
            } else {
              get_err()
            }
          },
          None => get_success(),
        }
      },
      _ => get_err(),
    },
    None => get_err(),
  }
}

pub fn match_literal(input: TokenTreeSlice) -> TtsIResult<Literal> {
  let get_err = || {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  };

  match input.split_first() {
    Some((first, rest)) => match first {
      TokenTree::Literal(literal) => Ok((rest, literal.clone())),
      _ => get_err(),
    },
    None => get_err(),
  }
}

pub fn match_literal_as_string(input: TokenTreeSlice) -> TtsIResult<String> {
  let filler_spaces = get_filler_spaces(input);
  match_literal(input).map(|(rest, lit)| (rest, format!("{}{}", lit.to_string(), filler_spaces)))
}

pub fn match_empty(input: TokenTreeSlice) -> TtsIResult<Vec<TokenStreamEventHandlingInfoPair>> {
  if input.len() == 0 {
    Ok((input, vec![]))
  } else {
    Err(nom::Err::Error(nom::error_position!(
      input,
      nom::ErrorKind::Custom(42)
    )))
  }
}

pub fn get_filler_spaces(input: TokenTreeSlice) -> String {
  let first_opt = input.get(0).map(|i| i.span().end());
  let second_opt = input.get(1).map(|i| i.span().start());
  match (first_opt, second_opt) {
    (Some(first), Some(second)) => {
      if first.line != second.line {
        "".into()
      } else {
        iter::repeat(" ")
          .take(second.column - first.column)
          .collect::<String>()
      }
    },
    _ => "".into(),
  }
}

fn reduce_vec_to_tokens(v: &Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
  let vec_contents = v
    .iter()
    .fold(quote!(), |existing, token| quote!(#existing #token,));
  quote!(vec![
    #vec_contents
  ])
}

pub fn reduce_vec_to_node(v: &Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
  let inner = reduce_vec_to_tokens(v);
  quote! {
    smithy::types::Node::Vec(#inner)
  }
}

pub fn enquote<T: ToTokens>(t: T) -> TokenStream {
  quote!(#t)
}