| use itertools::Itertools; |
| use proc_macro2::TokenStream; |
| use quote::{ToTokens, TokenStreamExt, quote}; |
| use serde_with::{DeserializeFromStr, SerializeDisplay}; |
| use std::str::pattern::Pattern; |
| use std::{fmt, str::FromStr}; |
| |
| use crate::context::LocalContext; |
| use crate::fn_suffix::make_neon_suffix; |
| use crate::typekinds::{ToRepr, TypeRepr}; |
| use crate::wildcards::Wildcard; |
| |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub enum WildStringPart { |
| String(String), |
| Wildcard(Wildcard), |
| } |
| |
| /// Wildcard-able string |
| #[derive(Debug, Clone, PartialEq, Eq, Default, SerializeDisplay, DeserializeFromStr)] |
| pub struct WildString(pub Vec<WildStringPart>); |
| |
| impl WildString { |
| pub fn has_wildcards(&self) -> bool { |
| for part in self.0.iter() { |
| if let WildStringPart::Wildcard(..) = part { |
| return true; |
| } |
| } |
| |
| false |
| } |
| |
| pub fn wildcards(&self) -> impl Iterator<Item = &Wildcard> + '_ { |
| self.0.iter().filter_map(|part| match part { |
| WildStringPart::Wildcard(w) => Some(w), |
| _ => None, |
| }) |
| } |
| |
| pub fn iter(&self) -> impl Iterator<Item = &WildStringPart> + '_ { |
| self.0.iter() |
| } |
| |
| pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WildStringPart> + '_ { |
| self.0.iter_mut() |
| } |
| |
| pub fn starts_with(&self, s2: &str) -> bool { |
| self.to_string().starts_with(s2) |
| } |
| |
| pub fn prepend_str(&mut self, s: impl Into<String>) { |
| self.0.insert(0, WildStringPart::String(s.into())) |
| } |
| |
| pub fn push_str(&mut self, s: impl Into<String>) { |
| self.0.push(WildStringPart::String(s.into())) |
| } |
| |
| pub fn push_wildcard(&mut self, w: Wildcard) { |
| self.0.push(WildStringPart::Wildcard(w)) |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.0.is_empty() |
| } |
| |
| pub fn replace<P>(&self, from: P, to: &str) -> WildString |
| where |
| P: Pattern + Copy, |
| { |
| WildString( |
| self.0 |
| .iter() |
| .map(|part| match part { |
| WildStringPart::String(s) => WildStringPart::String(s.replace(from, to)), |
| part => part.clone(), |
| }) |
| .collect_vec(), |
| ) |
| } |
| |
| pub fn build_acle(&mut self, ctx: &LocalContext) -> Result<(), String> { |
| self.build(ctx, TypeRepr::ACLENotation) |
| } |
| |
| pub fn build_neon_intrinsic_signature(&mut self, ctx: &LocalContext) -> Result<(), String> { |
| let repr = TypeRepr::ACLENotation; |
| self.iter_mut().try_for_each(|wp| -> Result<(), String> { |
| if let WildStringPart::Wildcard(w) = wp { |
| match w { |
| &mut Wildcard::NEONType(_, _, ref maybe_suffix_kind) => { |
| if let Some(suffix_kind) = maybe_suffix_kind { |
| let x = ctx.provide_type_wildcard(w).unwrap(); |
| *wp = WildStringPart::String(make_neon_suffix(x, *suffix_kind)) |
| } else { |
| *wp = WildString::make_default_build(ctx, repr, w) |
| } |
| } |
| _ => *wp = WildString::make_default_build(ctx, repr, w), |
| } |
| } |
| Ok(()) |
| }) |
| } |
| |
| pub fn build(&mut self, ctx: &LocalContext, repr: TypeRepr) -> Result<(), String> { |
| match repr { |
| TypeRepr::ACLENotation | TypeRepr::LLVMMachine => { |
| self.iter_mut().try_for_each(|wp| -> Result<(), String> { |
| if let WildStringPart::Wildcard(w) = wp { |
| match w { |
| &mut Wildcard::NEONType(_, _, ref maybe_suffix_kind) => { |
| if let Some(suffix_kind) = maybe_suffix_kind { |
| let x = ctx.provide_type_wildcard(w).unwrap(); |
| *wp = WildStringPart::String(make_neon_suffix(x, *suffix_kind)) |
| } else { |
| *wp = WildString::make_default_build(ctx, repr, w) |
| } |
| } |
| _ => *wp = WildString::make_default_build(ctx, repr, w), |
| } |
| } |
| Ok(()) |
| }) |
| } |
| _ => self.iter_mut().try_for_each(|wp| -> Result<(), String> { |
| if let WildStringPart::Wildcard(w) = wp { |
| *wp = WildString::make_default_build(ctx, repr, w); |
| } |
| Ok(()) |
| }), |
| } |
| } |
| |
| fn make_default_build(ctx: &LocalContext, repr: TypeRepr, w: &mut Wildcard) -> WildStringPart { |
| WildStringPart::String( |
| ctx.provide_substitution_wildcard(w) |
| .or_else(|_| ctx.provide_type_wildcard(w).map(|ty| ty.repr(repr))) |
| .unwrap(), |
| ) |
| } |
| } |
| |
| impl From<String> for WildString { |
| fn from(s: String) -> Self { |
| WildString(vec![WildStringPart::String(s)]) |
| } |
| } |
| |
| impl FromStr for WildString { |
| type Err = String; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| enum State { |
| Normal { start: usize }, |
| Wildcard { start: usize, count: usize }, |
| EscapeTokenOpen { start: usize, at: usize }, |
| EscapeTokenClose { start: usize, at: usize }, |
| } |
| |
| let mut ws = WildString::default(); |
| match s |
| .char_indices() |
| .try_fold(State::Normal { start: 0 }, |state, (idx, ch)| { |
| match (state, ch) { |
| (State::Normal { start }, '{') => Ok(State::EscapeTokenOpen { start, at: idx }), |
| (State::Normal { start }, '}') => { |
| Ok(State::EscapeTokenClose { start, at: idx }) |
| } |
| (State::EscapeTokenOpen { start, at }, '{') |
| | (State::EscapeTokenClose { start, at }, '}') => { |
| if start < at { |
| ws.push_str(&s[start..at]) |
| } |
| |
| Ok(State::Normal { start: idx }) |
| } |
| (State::EscapeTokenOpen { at, .. }, '}') => Err(format!( |
| "empty wildcard given in string {s:?} at position {at}" |
| )), |
| (State::EscapeTokenOpen { start, at }, _) => { |
| if start < at { |
| ws.push_str(&s[start..at]) |
| } |
| |
| Ok(State::Wildcard { |
| start: idx, |
| count: 0, |
| }) |
| } |
| (State::EscapeTokenClose { at, .. }, _) => Err(format!( |
| "closing a non-wildcard/bad escape in string {s:?} at position {at}" |
| )), |
| // Nesting wildcards is only supported for `{foo as {bar}}`, wildcards cannot be |
| // nested at the start of a WildString. |
| (State::Wildcard { start, count }, '{') => Ok(State::Wildcard { |
| start, |
| count: count + 1, |
| }), |
| (State::Wildcard { start, count: 0 }, '}') => { |
| ws.push_wildcard(s[start..idx].parse()?); |
| Ok(State::Normal { start: idx + 1 }) |
| } |
| (State::Wildcard { start, count }, '}') => Ok(State::Wildcard { |
| start, |
| count: count - 1, |
| }), |
| (state @ State::Normal { .. }, _) | (state @ State::Wildcard { .. }, _) => { |
| Ok(state) |
| } |
| } |
| })? { |
| State::Normal { start } => { |
| if start < s.len() { |
| ws.push_str(&s[start..]); |
| } |
| |
| Ok(ws) |
| } |
| State::EscapeTokenOpen { at, .. } | State::Wildcard { start: at, .. } => Err(format!( |
| "unclosed wildcard in string {s:?} at position {at}" |
| )), |
| State::EscapeTokenClose { at, .. } => Err(format!( |
| "closing a non-wildcard/bad escape in string {s:?} at position {at}" |
| )), |
| } |
| } |
| } |
| |
| impl fmt::Display for WildString { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!( |
| f, |
| "{}", |
| self.0 |
| .iter() |
| .map(|part| match part { |
| WildStringPart::String(s) => s.to_owned(), |
| WildStringPart::Wildcard(w) => format!("{{{w}}}"), |
| }) |
| .join("") |
| ) |
| } |
| } |
| |
| impl ToTokens for WildString { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| assert!( |
| !self.has_wildcards(), |
| "cannot convert string with wildcards {self:?} to TokenStream" |
| ); |
| let str = self.to_string(); |
| tokens.append_all(quote! { #str }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::typekinds::*; |
| use crate::wildstring::*; |
| |
| #[test] |
| fn test_empty_string() { |
| let ws: WildString = "".parse().unwrap(); |
| assert_eq!(ws.0.len(), 0); |
| } |
| |
| #[test] |
| fn test_plain_string() { |
| let ws: WildString = "plain string".parse().unwrap(); |
| assert_eq!(ws.0.len(), 1); |
| assert_eq!( |
| ws, |
| WildString(vec![WildStringPart::String("plain string".to_string())]) |
| ) |
| } |
| |
| #[test] |
| fn test_escaped_curly_brackets() { |
| let ws: WildString = "VALUE = {{value}}".parse().unwrap(); |
| assert_eq!(ws.to_string(), "VALUE = {value}"); |
| assert!(!ws.has_wildcards()); |
| } |
| |
| #[test] |
| fn test_escaped_curly_brackets_wildcard() { |
| let ws: WildString = "TYPE = {{{type}}}".parse().unwrap(); |
| assert_eq!(ws.to_string(), "TYPE = {{type}}"); |
| assert_eq!(ws.0.len(), 4); |
| assert!(ws.has_wildcards()); |
| } |
| |
| #[test] |
| fn test_wildcard_right_boundary() { |
| let s = "string test {type}"; |
| let ws: WildString = s.parse().unwrap(); |
| assert_eq!(&ws.to_string(), s); |
| assert!(ws.has_wildcards()); |
| } |
| |
| #[test] |
| fn test_wildcard_left_boundary() { |
| let s = "{type} string test"; |
| let ws: WildString = s.parse().unwrap(); |
| assert_eq!(&ws.to_string(), s); |
| assert!(ws.has_wildcards()); |
| } |
| |
| #[test] |
| fn test_recursive_wildcard() { |
| let s = "string test {type[0] as {type[1]}}"; |
| let ws: WildString = s.parse().unwrap(); |
| |
| assert_eq!(ws.0.len(), 2); |
| assert_eq!( |
| ws, |
| WildString(vec![ |
| WildStringPart::String("string test ".to_string()), |
| WildStringPart::Wildcard(Wildcard::Scale( |
| Box::new(Wildcard::Type(Some(0))), |
| Box::new(TypeKind::Wildcard(Wildcard::Type(Some(1)))), |
| )) |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn test_scale_wildcard() { |
| let s = "string {type[0] as i8} test"; |
| let ws: WildString = s.parse().unwrap(); |
| |
| assert_eq!(ws.0.len(), 3); |
| assert_eq!( |
| ws, |
| WildString(vec![ |
| WildStringPart::String("string ".to_string()), |
| WildStringPart::Wildcard(Wildcard::Scale( |
| Box::new(Wildcard::Type(Some(0))), |
| Box::new(TypeKind::Base(BaseType::Sized(BaseTypeKind::Int, 8))), |
| )), |
| WildStringPart::String(" test".to_string()) |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn test_solitaire_wildcard() { |
| let ws: WildString = "{type}".parse().unwrap(); |
| assert_eq!(ws.0.len(), 1); |
| assert_eq!( |
| ws, |
| WildString(vec![WildStringPart::Wildcard(Wildcard::Type(None))]) |
| ) |
| } |
| |
| #[test] |
| fn test_empty_wildcard() { |
| "string {}" |
| .parse::<WildString>() |
| .expect_err("expected parse error"); |
| } |
| |
| #[test] |
| fn test_invalid_open_wildcard_right() { |
| "string {" |
| .parse::<WildString>() |
| .expect_err("expected parse error"); |
| } |
| |
| #[test] |
| fn test_invalid_close_wildcard_right() { |
| "string }" |
| .parse::<WildString>() |
| .expect_err("expected parse error"); |
| } |
| |
| #[test] |
| fn test_invalid_open_wildcard_left() { |
| "{string" |
| .parse::<WildString>() |
| .expect_err("expected parse error"); |
| } |
| |
| #[test] |
| fn test_invalid_close_wildcard_left() { |
| "}string" |
| .parse::<WildString>() |
| .expect_err("expected parse error"); |
| } |
| |
| #[test] |
| fn test_consecutive_wildcards() { |
| let s = "svprf{size_literal[1]}_gather_{type[0]}{index_or_offset}"; |
| let ws: WildString = s.parse().unwrap(); |
| assert_eq!(ws.to_string(), s) |
| } |
| } |