//! Utility methods for transpiling use std::{fmt::Display, str::FromStr, sync::Arc}; use crate::{ base::{self, source_file::SourceElement as _, Handler}, lexical::token::{TemplateStringLiteral, TemplateStringLiteralPart}, syntax::syntax_tree::{ expression::{Expression, Primary}, AnyStringLiteral, }, transpile::{ error::{TranspileError, UnknownIdentifier}, Scope, TranspileResult, VariableData, }, }; /// String that can contain macros #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MacroString { /// A normal string String(String), /// A string containing expressions MacroString(Vec), } /// Part of a [`MacroString`] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MacroStringPart { /// A normal string String(String), /// A macro usage MacroUsage(String), } impl Display for MacroString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::String(s) => s.fmt(f), Self::MacroString(parts) => { for part in parts { match part { MacroStringPart::String(s) => s.fmt(f)?, MacroStringPart::MacroUsage(m) => write!(f, "$({m})")?, } } Ok(()) } } } } impl MacroString { /// Check if the macro string contains any macros #[must_use] pub fn contains_macros(&self) -> bool { match self { Self::String(_) => false, Self::MacroString(parts) => parts .iter() .any(|p| matches!(p, MacroStringPart::MacroUsage(_))), } } /// Get the string representation of the macro string or the parts if it contains macros /// /// # Errors /// - If the macro string contains macros pub fn as_str(&self) -> Result, &[MacroStringPart]> { match self { Self::String(s) => Ok(std::borrow::Cow::Borrowed(s)), Self::MacroString(parts) if self.contains_macros() => Err(parts), Self::MacroString(parts) => Ok(std::borrow::Cow::Owned( parts .iter() .map(|p| match p { MacroStringPart::String(s) => s.clone(), MacroStringPart::MacroUsage(m) => format!("$({m})"), }) .collect::(), )), } } } fn normalize_program_identifier(identifier: S) -> String where S: AsRef, { identifier .as_ref() .split('/') .fold(Vec::new(), |mut acc, el| match el { "." | "" => acc, ".." => { acc.pop(); acc } _ => { acc.push(el); acc } }) .join("/") } /// Calculate the identifier to import the function based on the current identifier and the import path #[must_use] pub fn calculate_import_identifier(current_identifier: S, import_path: T) -> String where S: AsRef, T: AsRef, { if import_path.as_ref().starts_with('/') { normalize_program_identifier(&import_path.as_ref()[1..]) } else { let mut identifier_elements = current_identifier.as_ref().split('/').collect::>(); identifier_elements.pop(); normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref()) } } /// Join multiple macro strings into one #[must_use] pub fn join_macro_strings(strings: I) -> MacroString where I: IntoIterator, { strings .into_iter() .fold(MacroString::String(String::new()), |acc, cur| match acc { MacroString::String(mut s) => match cur { MacroString::String(cur) => { s.push_str(&cur); MacroString::String(s) } MacroString::MacroString(cur) => { let mut parts = vec![MacroStringPart::String(s)]; parts.extend(cur); MacroString::MacroString(parts) } }, MacroString::MacroString(mut parts) => match cur { MacroString::String(cur) => { parts.push(MacroStringPart::String(cur)); MacroString::MacroString(parts) } MacroString::MacroString(cur) => { parts.extend(cur); MacroString::MacroString(parts) } }, }) } /// Add additional information to an entity selector #[must_use] pub fn add_to_entity_selector(selector: impl Into, additional: &str) -> String { let selector: String = selector.into(); if selector.starts_with('@') { if selector.ends_with(']') { selector[..selector.len() - 1].to_string() + "," + additional + "]" } else { selector + "[" + additional + "]" } } else { format!("@a[name={selector},{additional}]") } } impl FromStr for MacroString { type Err = (); fn from_str(s: &str) -> Result { let pos = s.find("$("); if pos.is_some_and(|pos| s[pos..].contains(')')) { let mut parts = Vec::new(); let mut rem = s; while let Some(pos) = rem.find("$(") { let (before, after) = rem.split_at(pos); let last_macro_index = after .char_indices() .skip(2) .take_while(|&(_, c)| c.is_ascii_alphanumeric() || c == '_') .map(|(i, _)| i) .last(); match last_macro_index { Some(last_macro_index) if after[last_macro_index + 1..].starts_with(')') => { if !before.is_empty() { match parts.last_mut() { Some(MacroStringPart::String(last)) => { *last += before; } _ => { parts.push(MacroStringPart::String(before.to_string())); } } } parts.push(MacroStringPart::MacroUsage( after[2..=last_macro_index].to_string(), )); rem = &after[last_macro_index + 2..]; if rem.is_empty() { break; } } _ => { let part = &rem[..=pos + 1]; match parts.last_mut() { Some(MacroStringPart::String(last)) => { *last += part; } _ => { parts.push(MacroStringPart::String(part.to_string())); } } rem = &rem[pos + 2..]; } } } if !rem.is_empty() { match parts.last_mut() { Some(MacroStringPart::String(last)) => { *last += rem; } _ => { parts.push(MacroStringPart::String(rem.to_string())); } } } if parts .iter() .any(|p| matches!(p, MacroStringPart::MacroUsage(_))) { Ok(Self::MacroString(parts)) } else { Ok(Self::String(s.to_string())) } } else { Ok(Self::String(s.to_string())) } } } impl From for MacroString where S: Into, { fn from(value: S) -> Self { Self::String(value.into()) } } impl AnyStringLiteral { /// Convert the any string literal to a macro string, using the provided scope to resolve variables /// /// # Errors /// - If an identifier in a template string is not found in the scope pub fn to_macro_string( &self, scope: &Arc, handler: &impl Handler, ) -> TranspileResult { match self { Self::StringLiteral(literal) => Ok(MacroString::from(literal.str_content().as_ref())), Self::TemplateStringLiteral(literal) => literal.to_macro_string(scope, handler), } } } impl TemplateStringLiteral { /// Convert the template string literal to a macro string, using the provided scope to resolve variables /// /// # Errors /// - If an identifier in a template string is not found in the scope pub fn to_macro_string( &self, scope: &Arc, handler: &impl Handler, ) -> TranspileResult { if self .parts() .iter() .any(|p| matches!(p, TemplateStringLiteralPart::Expression { .. })) { let macro_string = MacroString::MacroString( self.parts() .iter() .map(|part| match part { TemplateStringLiteralPart::Text(span) => Ok(MacroStringPart::String( crate::util::unescape_macro_string(span.str()).to_string(), )), TemplateStringLiteralPart::Expression { expression, .. } => { match expression { Expression::Primary(Primary::Identifier(identifier)) => { #[expect(clippy::option_if_let_else)] if let Some(var_data) = scope.get_variable(identifier.span.str()) { match var_data.as_ref() { VariableData::MacroParameter { macro_name, .. } => Ok( MacroStringPart::MacroUsage(macro_name.to_owned()), ), _ => todo!("other identifiers in template strings"), } } else { let err = TranspileError::UnknownIdentifier(UnknownIdentifier { identifier: identifier.span(), }); handler.receive(Box::new(err.clone())); Err(err) } } _ => todo!("other expressions in template strings"), } } }) .collect::>>()?, ); Ok(macro_string) } else { Ok(MacroString::String(self.str_content())) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_macro_string() { assert_eq!( MacroString::from_str("Hello, $(world)!").unwrap(), MacroString::MacroString(vec![ MacroStringPart::String("Hello, ".to_string()), MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("!".to_string()) ]) ); assert_eq!( MacroString::from_str("Hello, $(world)! $(world").unwrap(), MacroString::MacroString(vec![ MacroStringPart::String("Hello, ".to_string()), MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("! $(world".to_string()), ]) ); assert_eq!( MacroString::from_str("Hello $(a) from $(b) and $(c)").unwrap(), MacroString::MacroString(vec![ MacroStringPart::String("Hello ".to_string()), MacroStringPart::MacroUsage("a".to_string()), MacroStringPart::String(" from ".to_string()), MacroStringPart::MacroUsage("b".to_string()), MacroStringPart::String(" and ".to_string()), MacroStringPart::MacroUsage("c".to_string()), ]) ); assert_eq!( MacroString::from_str("Hello, $(world! $(world)!").unwrap(), MacroString::MacroString(vec![ MacroStringPart::String("Hello, $(world! ".to_string()), MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("!".to_string()), ]) ); } }