diff options
| author | tslil <tslil@posteo.de> | 2025-10-18 22:54:24 +0100 |
|---|---|---|
| committer | tslil <tslil@posteo.de> | 2025-10-18 23:52:52 +0100 |
| commit | 5cf06bab8d5a8c6115df35f6cec652bfd0fd0922 (patch) | |
| tree | ca412a1f1c0b58f464bb9f5583e028d403e9e63b /rprt-engine | |
| parent | 16f0479a4e43a58c04796e9aa4d43d92b2655024 (diff) | |
Parse hooks, these are left associative
Diffstat (limited to 'rprt-engine')
| -rw-r--r-- | rprt-engine/src/explainer.rs | 93 | ||||
| -rw-r--r-- | rprt-engine/src/expression.rs | 2 | ||||
| -rw-r--r-- | rprt-engine/src/parser.rs | 612 |
3 files changed, 562 insertions, 145 deletions
diff --git a/rprt-engine/src/explainer.rs b/rprt-engine/src/explainer.rs index 9c847f2..38bd41b 100644 --- a/rprt-engine/src/explainer.rs +++ b/rprt-engine/src/explainer.rs @@ -1,6 +1,6 @@ use rprt_engine::{expression::*, parser::*, token::*}; -use std::env; use std::collections::BTreeSet; +use std::env; fn main() { let args: Vec<String> = env::args().collect(); @@ -37,6 +37,8 @@ struct TreeNode { depth: usize, children: Vec<usize>, is_leaf: bool, + hook_kind: Option<HookKind>, + train_arity: Option<u8>, } fn generate_tree(formatted: &str, expr: &Composite) -> String { @@ -51,11 +53,8 @@ fn generate_tree(formatted: &str, expr: &Composite) -> String { output.push_str(formatted); output.push('\n'); - let mut active_bars: BTreeSet<usize> = nodes - .iter() - .filter(|n| n.is_leaf) - .map(|n| n.pos) - .collect(); + let mut active_bars: BTreeSet<usize> = + nodes.iter().filter(|n| n.is_leaf).map(|n| n.pos).collect(); let mut prev_active = active_bars.clone(); @@ -64,7 +63,11 @@ fn generate_tree(formatted: &str, expr: &Composite) -> String { .iter() .filter(|n| n.depth == current_depth && !n.is_leaf) .collect(); - + + if interior.is_empty() { + continue; + } + let mut next_active = active_bars.clone(); for node in &interior { for &child in &node.children { @@ -82,15 +85,40 @@ fn generate_tree(formatted: &str, expr: &Composite) -> String { } } + let mut spacer = vec![' '; width]; + for i in 0..width { + if prev_active.contains(&i) { + spacer[i] = '│'; + } + } + output.push_str(&spacer.iter().collect::<String>()); + output.push('\n'); + let mut line = vec![' '; width]; for i in 0..width { let up = prev_active.contains(&i); let down = next_active.contains(&i); let in_span = horizontal_spans.iter().any(|(l, r)| *l <= i && i <= *r); - let left = i > 0 && horizontal_spans.iter().any(|(l, r)| *l <= i - 1 && i - 1 <= *r); - let right = i + 1 < width && horizontal_spans.iter().any(|(l, r)| *l <= i + 1 && i + 1 <= *r); - - if up || down || in_span { + let left = i > 0 + && horizontal_spans + .iter() + .any(|(l, r)| *l <= i - 1 && i - 1 <= *r); + let right = i + 1 < width + && horizontal_spans + .iter() + .any(|(l, r)| *l <= i + 1 && i + 1 <= *r); + + let node_at_pos = interior.iter().find(|n| n.pos == i); + + if let Some(node) = node_at_pos { + if let Some(hook_kind) = node.hook_kind { + line[i] = hook_kind.to_string().chars().next().unwrap(); + } else if let Some(arity) = node.train_arity { + line[i] = char::from_digit(arity as u32, 10).unwrap(); + } else if up || down || in_span { + line[i] = box_char(up, down, left, right); + } + } else if up || down || in_span { line[i] = box_char(up, down, left, right); } } @@ -116,7 +144,16 @@ fn generate_tree(formatted: &str, expr: &Composite) -> String { output } -fn build_tree_from_expr(expr: &Composite, depth: usize, offset: usize, nodes: &mut Vec<TreeNode>) -> usize { +fn format_width(expr: &Composite) -> usize { + format!("{}", expr).chars().count() +} + +fn build_tree_from_expr( + expr: &Composite, + depth: usize, + offset: usize, + nodes: &mut Vec<TreeNode>, +) -> usize { match expr { Composite::SelectionFunction { .. } | Composite::TextFunction { .. } => { nodes.push(TreeNode { @@ -124,15 +161,15 @@ fn build_tree_from_expr(expr: &Composite, depth: usize, offset: usize, nodes: &m depth, children: vec![], is_leaf: true, + hook_kind: None, + train_arity: None, }); offset } Composite::Train2 { f, g } => { - let f_offset = offset + 1; + let f_offset = offset + 1; // Account for opening '(' let f_pos = build_tree_from_expr(f, depth + 1, f_offset, nodes); - - let f_str = format!("{}", f); - let g_offset = f_offset + f_str.chars().count() + 1; + let g_offset = f_offset + format_width(f) + 1; // +1 for space let g_pos = build_tree_from_expr(g, depth + 1, g_offset, nodes); nodes.push(TreeNode { @@ -140,19 +177,17 @@ fn build_tree_from_expr(expr: &Composite, depth: usize, offset: usize, nodes: &m depth, children: vec![f_pos, g_pos], is_leaf: false, + hook_kind: None, + train_arity: Some(2), }); f_pos } Composite::Train3 { f, g, h } => { - let f_offset = offset + 1; + let f_offset = offset + 1; // Account for opening '(' let f_pos = build_tree_from_expr(f, depth + 1, f_offset, nodes); - - let f_str = format!("{}", f); - let g_offset = f_offset + f_str.chars().count() + 1; + let g_offset = f_offset + format_width(f) + 1; // +1 for space let g_pos = build_tree_from_expr(g, depth + 1, g_offset, nodes); - - let g_str = format!("{}", g); - let h_offset = g_offset + g_str.chars().count() + 1; + let h_offset = g_offset + format_width(g) + 1; // +1 for space let h_pos = build_tree_from_expr(h, depth + 1, h_offset, nodes); nodes.push(TreeNode { @@ -160,15 +195,15 @@ fn build_tree_from_expr(expr: &Composite, depth: usize, offset: usize, nodes: &m depth, children: vec![f_pos, g_pos, h_pos], is_leaf: false, + hook_kind: None, + train_arity: Some(3), }); g_pos } Composite::Hook { kind, left, right } => { - let left_pos = build_tree_from_expr(left, depth + 1, offset, nodes); - - let left_str = format!("{}", left); - let kind_str = format!("{}", kind); - let right_offset = offset + left_str.chars().count() + kind_str.chars().count(); + let left_offset = offset + 1; // Account for opening '(' + let left_pos = build_tree_from_expr(left, depth + 1, left_offset, nodes); + let right_offset = left_offset + format_width(left) + 1; // +1 for hook operator (< or >) let right_pos = build_tree_from_expr(right, depth + 1, right_offset, nodes); nodes.push(TreeNode { @@ -176,6 +211,8 @@ fn build_tree_from_expr(expr: &Composite, depth: usize, offset: usize, nodes: &m depth, children: vec![left_pos, right_pos], is_leaf: false, + hook_kind: Some(*kind), + train_arity: None, }); left_pos } diff --git a/rprt-engine/src/expression.rs b/rprt-engine/src/expression.rs index c463fca..ba433cf 100644 --- a/rprt-engine/src/expression.rs +++ b/rprt-engine/src/expression.rs @@ -51,7 +51,7 @@ impl Display for Composite { TextFunction { func, swapped } => { write!(fmtr, "{}{func}", if *swapped { "@" } else { "" }) } - Hook { kind, left, right } => write!(fmtr, "{left}{kind}{right}",), + 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})"), } diff --git a/rprt-engine/src/parser.rs b/rprt-engine/src/parser.rs index 78c28c0..c17913a 100644 --- a/rprt-engine/src/parser.rs +++ b/rprt-engine/src/parser.rs @@ -46,12 +46,12 @@ pub fn parse(tokens: Vec<Token>) -> Result<Composite, ParseError> { tokens: tokens, pos: 0, }; - parse_function_in_application(&mut context) + parse_composition(&mut context) } -fn parse_function_in_application(context: &mut ParseContext) -> Result<Composite, ParseError> { +fn parse_composition(context: &mut ParseContext) -> Result<Composite, ParseError> { let mut functions = Vec::new(); - while can_start_selection_function(context, 0) { + while can_start_composition(context, 0) { functions.push(parse_function_atom(context)?); } if functions.is_empty() { @@ -103,7 +103,7 @@ fn build_train(mut functions: Vec<Composite>) -> Composite { } } -fn can_start_selection_function(context: &ParseContext, offset: usize) -> bool { +fn can_start_composition(context: &ParseContext, offset: usize) -> bool { matches!( context.peek(offset), Some(Token::Empty) @@ -120,13 +120,22 @@ fn can_start_selection_function(context: &ParseContext, offset: usize) -> bool { | Some(Token::Complement) | Some(Token::Conditional) | Some(Token::LParen) + | Some(Token::Dot) + | Some(Token::Change) + | Some(Token::Insert) + | Some(Token::Append) + | Some(Token::Delete) + | Some(Token::Write) + | Some(Token::Pipe) + | Some(Token::TextLiteral(_)) + | Some(Token::Swap) ) } -fn parse_function_atom(context: &mut ParseContext) -> Result<Composite, ParseError> { +fn parse_base_function(context: &mut ParseContext) -> Result<Composite, ParseError> { if matches!(context.current()?, Token::LParen) { context.advance(1, true)?; - let func = parse_function_in_application(context)?; + let func = parse_composition(context)?; if !matches!(context.current()?, Token::RParen) { return Err(ParseError::UnexpectedToken { found: context.current()?, @@ -134,143 +143,230 @@ fn parse_function_atom(context: &mut ParseContext) -> Result<Composite, ParseErr }); } context.advance(1, false)?; - return Ok(func); + Ok(func) + } else if is_function_t(context)? { + parse_builtin_function_t(context) + } else if is_function_s(context)? { + parse_builtin_function_s(context) + } else { + Err(ParseError::UnexpectedToken { + found: context.current()?, + expecting: "function", + }) } - let result_trans = parse_result_trans(context)?; - let search_mod = parse_search_mod(context)?; - let func = parse_builtin_function_s(context)?; - Ok(Composite::SelectionFunction { - func: func, - search_mod: search_mod, - result_transform: result_trans, - }) } -fn parse_search_mod(context: &mut ParseContext) -> Result<Option<SearchModifier>, ParseError> { - let maybe_mod = |token| match token { - Token::Sequential => Ok(Some(SearchModifier::Sequential)), - Token::Reverse => Ok(Some(SearchModifier::Reverse)), - _ => Ok(None), - }; - let token = context.current()?; - if let Some(m1) = maybe_mod(token)? { - context.advance(1, true)?; - if let Some(m2) = maybe_mod(context.current()?)? { - context.advance(1, true)?; - return Ok(Some(match (m1, m2) { - (SearchModifier::Sequential, SearchModifier::Reverse) => { - SearchModifier::SequentialReverse - } - (SearchModifier::Reverse, SearchModifier::Sequential) => { - SearchModifier::ReverseSequential - } - (_, _) => unreachable!(), - })); +fn parse_function_atom(context: &mut ParseContext) -> Result<Composite, ParseError> { + let mut result = parse_base_function(context)?; + + loop { + match context.current() { + Ok(Token::Before) => { + context.advance(1, true)?; + let right = parse_base_function(context)?; + result = Composite::Hook { + kind: HookKind::Before, + left: Box::new(result), + right: Box::new(right), + }; + } + Ok(Token::After) => { + context.advance(1, true)?; + let right = parse_base_function(context)?; + result = Composite::Hook { + kind: HookKind::After, + left: Box::new(result), + right: Box::new(right), + }; + } + _ => return Ok(result), } - return Ok(Some(m1)); - }; - Ok(None) + } } -fn parse_result_trans(context: &mut ParseContext) -> Result<Option<ResultTransform>, ParseError> { - let maybe_mod = |token| match token { - Token::Complement => Ok(Some(ResultTransform::Complement)), - Token::Conditional => Ok(Some(ResultTransform::Conditional)), - _ => Ok(None), - }; - let token = context.current()?; - if let Some(m1) = maybe_mod(token)? { - context.advance(1, true)?; - if let Some(m2) = maybe_mod(context.current()?)? { - context.advance(1, true)?; - return Ok(Some(match (m1, m2) { - (ResultTransform::Complement, ResultTransform::Conditional) => { - ResultTransform::ComplementConditional - } - (ResultTransform::Conditional, ResultTransform::Complement) => { - ResultTransform::ConditionalComplement - } - (_, _) => unreachable!(), - })); - } - return Ok(Some(m1)); - }; - Ok(None) +fn is_function_t(context: &ParseContext) -> Result<bool, ParseError> { + Ok(matches!( + context.current()?, + Token::Dot + | Token::Change + | Token::Insert + | Token::Append + | Token::Delete + | Token::Write + | Token::Pipe + | Token::TextLiteral(_) + | Token::Swap + )) } -fn parse_builtin_function_s(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> { +fn parse_builtin_function_t(context: &mut ParseContext) -> Result<Composite, ParseError> { + let mut swapped = false; + if context.current()? == Token::Swap { + swapped = true; + context.advance(1, true)?; + } let token = context.current()?; let result = match token { - Token::Empty => BuiltinSelectionFn::Empty, - Token::EndOfBuffer => BuiltinSelectionFn::EndOfBuffer, - Token::Span => BuiltinSelectionFn::Span, - Token::CharacterOffset => return parse_char_offset(context), - Token::Number(n) => BuiltinSelectionFn::Line(n), - Token::Regex(pattern) => BuiltinSelectionFn::Regex(pattern), - Token::AllMatches => return parse_all_matches(context), - Token::LineOffset => return parse_relative_line(context), - Token::BufferMatch => return parse_buffer_match(context), + Token::Dot => BuiltinTextFn::Dot, + Token::Change => BuiltinTextFn::Change, + Token::Insert => BuiltinTextFn::Insert, + Token::Append => BuiltinTextFn::Append, + Token::Delete => BuiltinTextFn::Delete, + Token::Write => BuiltinTextFn::Write, + Token::Pipe => BuiltinTextFn::Pipe, + Token::TextLiteral(s) => BuiltinTextFn::Literal(s), _ => { return Err(ParseError::UnexpectedToken { found: token, - expecting: "selection function", + expecting: "text function", }); } }; context.advance(1, false)?; - Ok(result) + Ok(Composite::TextFunction { + func: result, + swapped: swapped, + }) } -fn parse_char_offset(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> { - context.advance(1, true)?; - let token = context.current()?; - context.advance(1, false)?; - match token { - Token::Number(n) => Ok(BuiltinSelectionFn::CharOffset(n)), - _ => Err(ParseError::UnexpectedToken { - found: token, - expecting: "number", - }), - } +fn is_function_s(context: &ParseContext) -> Result<bool, ParseError> { + Ok(matches!( + context.current()?, + Token::Empty + | Token::EndOfBuffer + | Token::Span + | Token::CharacterOffset + | Token::Number(_) + | Token::Regex(_) + | Token::AllMatches + | Token::LineOffset + | Token::BufferMatch + | Token::Reverse + | Token::Sequential + | Token::Complement + | Token::Conditional + | Token::LParen + )) } -fn parse_all_matches(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> { - context.advance(1, true)?; - let token = context.current()?; - context.advance(1, false)?; - match token { - Token::Regex(pattern) => Ok(BuiltinSelectionFn::AllMatches(pattern)), - _ => Err(ParseError::UnexpectedToken { - found: token, - expecting: "regex", - }), +fn parse_builtin_function_s(context: &mut ParseContext) -> Result<Composite, ParseError> { + fn parse_modifier_pair<T: Copy>( + context: &mut ParseContext, + parse_single: impl Fn(Token) -> Option<T>, + combine: impl Fn(T, T) -> T, + ) -> Result<Option<T>, ParseError> { + let token = context.current()?; + if let Some(m1) = parse_single(token) { + context.advance(1, true)?; + if let Some(m2) = parse_single(context.current()?) { + context.advance(1, true)?; + return Ok(Some(combine(m1, m2))); + } + return Ok(Some(m1)); + } + Ok(None) } -} -fn parse_relative_line(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> { - context.advance(1, true)?; - let token = context.current()?; - context.advance(1, false)?; - match token { - Token::Number(n) => Ok(BuiltinSelectionFn::RelativeLine(n)), - _ => Err(ParseError::UnexpectedToken { + fn parse_prefixed_builtin( + context: &mut ParseContext, + expected: &'static str, + matcher: impl FnOnce(Token) -> Option<BuiltinSelectionFn>, + ) -> Result<BuiltinSelectionFn, ParseError> { + context.advance(1, true)?; + let token = context.current()?; + context.advance(1, false)?; + matcher(token.clone()).ok_or(ParseError::UnexpectedToken { found: token, - expecting: "number", - }), + expecting: expected, + }) } -} -fn parse_buffer_match(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> { - context.advance(1, true)?; + let result_trans = parse_modifier_pair( + context, + |token| match token { + Token::Complement => Some(ResultTransform::Complement), + Token::Conditional => Some(ResultTransform::Conditional), + _ => None, + }, + |m1, m2| match (m1, m2) { + (ResultTransform::Complement, ResultTransform::Conditional) => { + ResultTransform::ComplementConditional + } + (ResultTransform::Conditional, ResultTransform::Complement) => { + ResultTransform::ConditionalComplement + } + _ => unreachable!(), + }, + )?; + + let search_mod = parse_modifier_pair( + context, + |token| match token { + Token::Sequential => Some(SearchModifier::Sequential), + Token::Reverse => Some(SearchModifier::Reverse), + _ => None, + }, + |m1, m2| match (m1, m2) { + (SearchModifier::Sequential, SearchModifier::Reverse) => { + SearchModifier::SequentialReverse + } + (SearchModifier::Reverse, SearchModifier::Sequential) => { + SearchModifier::ReverseSequential + } + _ => unreachable!(), + }, + )?; + let token = context.current()?; - context.advance(1, false)?; - match token { - Token::Regex(pattern) => Ok(BuiltinSelectionFn::BufferMatch(pattern)), - _ => Err(ParseError::UnexpectedToken { - found: token, - expecting: "regex", - }), - } + let func = match token { + Token::Empty => { + context.advance(1, false)?; + BuiltinSelectionFn::Empty + } + Token::EndOfBuffer => { + context.advance(1, false)?; + BuiltinSelectionFn::EndOfBuffer + } + Token::Span => { + context.advance(1, false)?; + BuiltinSelectionFn::Span + } + Token::CharacterOffset => parse_prefixed_builtin(context, "number", |t| match t { + Token::Number(n) => Some(BuiltinSelectionFn::CharOffset(n)), + _ => None, + })?, + Token::Number(n) => { + context.advance(1, false)?; + BuiltinSelectionFn::Line(n) + } + Token::Regex(pattern) => { + context.advance(1, false)?; + BuiltinSelectionFn::Regex(pattern) + } + Token::AllMatches => parse_prefixed_builtin(context, "regex", |t| match t { + Token::Regex(pattern) => Some(BuiltinSelectionFn::AllMatches(pattern)), + _ => None, + })?, + Token::LineOffset => parse_prefixed_builtin(context, "number", |t| match t { + Token::Number(n) => Some(BuiltinSelectionFn::RelativeLine(n)), + _ => None, + })?, + Token::BufferMatch => parse_prefixed_builtin(context, "regex", |t| match t { + Token::Regex(pattern) => Some(BuiltinSelectionFn::BufferMatch(pattern)), + _ => None, + })?, + _ => { + return Err(ParseError::UnexpectedToken { + found: token, + expecting: "selection function", + }); + } + }; + return Ok(Composite::SelectionFunction { + func: func, + search_mod: search_mod, + result_transform: result_trans, + }); } #[cfg(test)] @@ -494,4 +590,288 @@ mod tests { _ => panic!(), } } + + #[test] + fn test_parse_train_selection_and_text() { + let input = r#""replacement" @c /pattern/"#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Train3 { f, g, h } => { + match *f { + Composite::TextFunction { + func: BuiltinTextFn::Literal(ref s), + swapped: false, + } => assert_eq!(s, "replacement"), + _ => panic!(), + } + + assert!(matches!( + *g, + Composite::TextFunction { + func: BuiltinTextFn::Change, + swapped: true, + } + )); + + match *h { + Composite::SelectionFunction { + func: BuiltinSelectionFn::Regex(ref pattern), + search_mod: None, + result_transform: None, + } => assert_eq!(pattern, "pattern"), + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_train_with_pipe() { + let input = r#"| "date""#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + match result { + Composite::Train2 { f, g } => { + assert!(matches!( + *f, + Composite::TextFunction { + func: BuiltinTextFn::Pipe, + swapped: false, + } + )); + match *g { + Composite::TextFunction { + func: BuiltinTextFn::Literal(ref s), + swapped: false, + } => assert_eq!(s, "date"), + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_before_hook() { + let input = "e>d"; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Hook { kind, left, right } => { + assert_eq!(kind, HookKind::Before); + assert!(matches!( + *left, + Composite::SelectionFunction { + func: BuiltinSelectionFn::Empty, + search_mod: None, + result_transform: None, + } + )); + assert!(matches!( + *right, + Composite::TextFunction { + func: BuiltinTextFn::Delete, + swapped: false, + } + )); + } + _ => panic!(), + } + } + + #[test] + fn test_parse_nested_hooks_left_associative() { + let input = r#"c<(|<"sort")"#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Hook { + kind: outer_kind, + left: outer_left, + right: outer_right, + } => { + assert_eq!(outer_kind, HookKind::After); + assert!(matches!( + *outer_left, + Composite::TextFunction { + func: BuiltinTextFn::Change, + swapped: false, + } + )); + match *outer_right { + Composite::Hook { + kind: inner_kind, + left: inner_left, + right: inner_right, + } => { + assert_eq!(inner_kind, HookKind::After); + assert!(matches!( + *inner_left, + Composite::TextFunction { + func: BuiltinTextFn::Pipe, + swapped: false, + } + )); + match *inner_right { + Composite::TextFunction { + func: BuiltinTextFn::Literal(ref s), + swapped: false, + } => assert_eq!(s, "sort"), + _ => panic!(), + } + } + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_hooks_without_parens() { + let input = r#"c<|<"sort""#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Hook { + kind: outer_kind, + left: outer_left, + right: outer_right, + } => { + assert_eq!(outer_kind, HookKind::After); + match *outer_left { + Composite::Hook { + kind: inner_kind, + left: inner_left, + right: inner_right, + } => { + assert_eq!(inner_kind, HookKind::After); + assert!(matches!( + *inner_left, + Composite::TextFunction { + func: BuiltinTextFn::Change, + swapped: false, + } + )); + assert!(matches!( + *inner_right, + Composite::TextFunction { + func: BuiltinTextFn::Pipe, + swapped: false, + } + )); + } + _ => panic!(), + } + match *outer_right { + Composite::TextFunction { + func: BuiltinTextFn::Literal(ref s), + swapped: false, + } => assert_eq!(s, "sort"), + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_triple_nested_hooks() { + let input = "e>d<c>i"; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Hook { + kind: k1, + left: l1, + right: r1, + } => { + assert_eq!(k1, HookKind::Before); + assert!(matches!( + *r1, + Composite::TextFunction { + func: BuiltinTextFn::Insert, + swapped: false, + } + )); + match *l1 { + Composite::Hook { + kind: k2, + left: l2, + right: r2, + } => { + assert_eq!(k2, HookKind::After); + assert!(matches!( + *r2, + Composite::TextFunction { + func: BuiltinTextFn::Change, + swapped: false, + } + )); + match *l2 { + Composite::Hook { + kind: k3, + left: l3, + right: r3, + } => { + assert_eq!(k3, HookKind::Before); + assert!(matches!( + *l3, + Composite::SelectionFunction { + func: BuiltinSelectionFn::Empty, + search_mod: None, + result_transform: None, + } + )); + assert!(matches!( + *r3, + Composite::TextFunction { + func: BuiltinTextFn::Delete, + swapped: false, + } + )); + } + _ => panic!(), + } + } + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_hook_in_train() { + let input = "e>d $"; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + println!("{:?}", result); + match result { + Composite::Train2 { f, g } => { + match *f { + Composite::Hook { kind, .. } => { + assert_eq!(kind, HookKind::Before); + } + _ => panic!(), + } + assert!(matches!( + *g, + Composite::SelectionFunction { + func: BuiltinSelectionFn::EndOfBuffer, + search_mod: None, + result_transform: None, + } + )); + } + _ => panic!(), + } + } } |
