From 5cf06bab8d5a8c6115df35f6cec652bfd0fd0922 Mon Sep 17 00:00:00 2001 From: tslil Date: Sat, 18 Oct 2025 22:54:24 +0100 Subject: Parse hooks, these are left associative --- rprt-engine/src/parser.rs | 612 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 496 insertions(+), 116 deletions(-) (limited to 'rprt-engine/src/parser.rs') 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) -> Result { tokens: tokens, pos: 0, }; - parse_function_in_application(&mut context) + parse_composition(&mut context) } -fn parse_function_in_application(context: &mut ParseContext) -> Result { +fn parse_composition(context: &mut ParseContext) -> Result { 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 { } } -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 { +fn parse_base_function(context: &mut ParseContext) -> Result { 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 Result, 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 { + 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, 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 { + 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 { +fn parse_builtin_function_t(context: &mut ParseContext) -> Result { + 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 { - 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 { + 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 { - 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 { + fn parse_modifier_pair( + context: &mut ParseContext, + parse_single: impl Fn(Token) -> Option, + combine: impl Fn(T, T) -> T, + ) -> Result, 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 { - 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, + ) -> Result { + 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 { - 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>di"; + 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!(), + } + } } -- cgit v1.2.3