use std::fmt::Display; #[derive(Debug, Clone, PartialEq)] pub enum Composite { SelectionFunction { func: BuiltinSelectionFn, search_mod: Option, result_transform: Option, }, TextFunction { func: BuiltinTextFn, swapped: bool, }, Hook { kind: HookKind, left: Box, right: Box, }, Train2 { f: Box, g: Box, }, Train3 { f: Box, g: Box, h: Box, }, Group { operations: Vec, }, } impl Display for Composite { fn fmt(&self, fmtr: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::expression::Composite::*; fn s(x: &Option) -> String { if let Some(x) = x { x.to_string() } else { String::new() } } match self { SelectionFunction { func, search_mod, result_transform, } => write!(fmtr, "{}{}{func}", s(result_transform), s(search_mod)), TextFunction { func, swapped } => { write!(fmtr, "{}{func}", if *swapped { "@" } else { "" }) } Hook { kind, left, right } => write!(fmtr, "({left}{kind}{right})"), Train2 { f, g } => write!(fmtr, "({f} {g})"), Train3 { f, g, h } => write!(fmtr, "({f} {g} {h})"), Group { operations } => { write!(fmtr, "{{")?; for (i, op) in operations.iter().enumerate() { if i > 0 { write!(fmtr, " , ")?; } write!(fmtr, "{}", op)?; } write!(fmtr, "}}") } } } } #[derive(Debug, Clone, PartialEq)] pub enum BuiltinSelectionFn { Empty, EndOfBuffer, Span, CharOffset(usize), Line(usize), Regex(String), AllMatches(String), RelativeLine(usize), BufferMatch(String), } impl Display for BuiltinSelectionFn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::expression::BuiltinSelectionFn::*; match self { Empty => write!(f, "e"), EndOfBuffer => write!(f, "$"), Span => write!(f, "-"), CharOffset(pos) => write!(f, "#{}", pos), Line(pos) => write!(f, "{}", pos), Regex(pat) => write!(f, "/{}/", pat), AllMatches(pat) => write!(f, "x/{}/", pat), RelativeLine(pos) => write!(f, "+{}", pos), BufferMatch(pat) => write!(f, "B/{}/", pat), } } } #[derive(Debug, Clone, PartialEq)] pub enum BuiltinTextFn { Dot, Change, Insert, Append, Delete, Write, Pipe, Literal(String), } impl Display for BuiltinTextFn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::expression::BuiltinTextFn::*; match self { Dot => write!(f, "."), Change => write!(f, "c"), Insert => write!(f, "i"), Append => write!(f, "a"), Delete => write!(f, "d"), Write => write!(f, "w"), Pipe => write!(f, "|"), Literal(s) => write!(f, "\"{}\"", s), } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum SearchModifier { Reverse, Sequential, ReverseSequential, SequentialReverse, } impl Display for SearchModifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::expression::SearchModifier::*; match self { Reverse => write!(f, "'"), Sequential => write!(f, ";"), ReverseSequential => write!(f, "';"), SequentialReverse => write!(f, ";'"), } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum ResultTransform { Complement, Conditional, ComplementConditional, ConditionalComplement, } impl Display for ResultTransform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::expression::ResultTransform::*; match self { Complement => write!(f, "~"), Conditional => write!(f, "?"), ComplementConditional => write!(f, "~?"), ConditionalComplement => write!(f, "?~"), } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum HookKind { Before, After, } impl Display for HookKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { HookKind::Before => write!(f, ">"), HookKind::After => write!(f, "<"), } } } #[cfg(test)] mod tests { use crate::expression::*; #[test] fn format_3train() { // nonsense, but it exercises the code let comp = Composite::Train3 { f: Box::new(Composite::SelectionFunction { func: BuiltinSelectionFn::AllMatches(String::from("re")), result_transform: Some(ResultTransform::Complement), search_mod: Some(SearchModifier::Reverse), }), g: Box::new(Composite::Train2 { f: Box::new(Composite::TextFunction { func: BuiltinTextFn::Change, swapped: false, }), g: Box::new(Composite::SelectionFunction { func: BuiltinSelectionFn::Line(5), result_transform: None, search_mod: None, }), }), h: Box::new(Composite::Hook { kind: HookKind::Before, left: Box::new(Composite::SelectionFunction { func: BuiltinSelectionFn::Empty, result_transform: None, search_mod: None, }), right: Box::new(Composite::TextFunction { func: BuiltinTextFn::Delete, swapped: false, }), }), }; let formatted = format!("{}", comp); println!("Formatted: {}", formatted); assert_eq!(formatted, "(~'x/re/ (c 5) (e>d))"); } }