diff options
Diffstat (limited to 'rprt-engine/src')
| -rw-r--r-- | rprt-engine/src/explainer.rs | 28 | ||||
| -rw-r--r-- | rprt-engine/src/expression.rs | 16 | ||||
| -rw-r--r-- | rprt-engine/src/parser.rs | 162 |
3 files changed, 203 insertions, 3 deletions
diff --git a/rprt-engine/src/explainer.rs b/rprt-engine/src/explainer.rs index 38bd41b..b62bee4 100644 --- a/rprt-engine/src/explainer.rs +++ b/rprt-engine/src/explainer.rs @@ -201,9 +201,9 @@ fn build_tree_from_expr( g_pos } Composite::Hook { kind, left, right } => { - let left_offset = offset + 1; // Account for opening '(' + let left_offset = offset + 1; 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_offset = left_offset + format_width(left) + 1; let right_pos = build_tree_from_expr(right, depth + 1, right_offset, nodes); nodes.push(TreeNode { @@ -216,6 +216,30 @@ fn build_tree_from_expr( }); left_pos } + Composite::Group { operations } => { + let mut children = Vec::new(); + let mut current_offset = offset + 1; + + for (i, op) in operations.iter().enumerate() { + if i > 0 { + current_offset += 3; + } + let pos = build_tree_from_expr(op, depth + 1, current_offset, nodes); + children.push(pos); + current_offset += format_width(op); + } + + let node_pos = children.first().copied().unwrap_or(offset); + nodes.push(TreeNode { + pos: node_pos, + depth, + children, + is_leaf: false, + hook_kind: None, + train_arity: None, + }); + node_pos + } } } diff --git a/rprt-engine/src/expression.rs b/rprt-engine/src/expression.rs index ba433cf..fcb1ff1 100644 --- a/rprt-engine/src/expression.rs +++ b/rprt-engine/src/expression.rs @@ -29,6 +29,10 @@ pub enum Composite { g: Box<Composite>, h: Box<Composite>, }, + + Group { + operations: Vec<Composite>, + }, } impl Display for Composite { @@ -54,6 +58,16 @@ impl Display for Composite { 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, "}}") + } } } } @@ -212,6 +226,6 @@ mod tests { let formatted = format!("{}", comp); println!("Formatted: {}", formatted); - assert_eq!(formatted, "(~'x/re/ (c 5) e>d)"); + assert_eq!(formatted, "(~'x/re/ (c 5) (e>d))"); } } diff --git a/rprt-engine/src/parser.rs b/rprt-engine/src/parser.rs index c17913a..9536046 100644 --- a/rprt-engine/src/parser.rs +++ b/rprt-engine/src/parser.rs @@ -120,6 +120,7 @@ fn can_start_composition(context: &ParseContext, offset: usize) -> bool { | Some(Token::Complement) | Some(Token::Conditional) | Some(Token::LParen) + | Some(Token::LBrace) | Some(Token::Dot) | Some(Token::Change) | Some(Token::Insert) @@ -144,6 +145,21 @@ fn parse_base_function(context: &mut ParseContext) -> Result<Composite, ParseErr } context.advance(1, false)?; Ok(func) + } else if matches!(context.current()?, Token::LBrace) { + context.advance(1, true)?; + let mut operations = vec![parse_composition(context)?]; + while matches!(context.current(), Ok(Token::Comma)) { + context.advance(1, true)?; + operations.push(parse_composition(context)?); + } + if !matches!(context.current()?, Token::RBrace) { + return Err(ParseError::UnexpectedToken { + found: context.current()?, + expecting: "}", + }); + } + context.advance(1, false)?; + Ok(Composite::Group { operations }) } else if is_function_t(context)? { parse_builtin_function_t(context) } else if is_function_s(context)? { @@ -874,4 +890,150 @@ mod tests { _ => panic!(), } } + + #[test] + fn test_parse_group() { + let input = r#"{e, #5 -, "tr"}"#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + match result { + Composite::Group { operations } => { + assert_eq!(operations.len(), 3); + match &operations[0] { + Composite::SelectionFunction { + func: BuiltinSelectionFn::Empty, + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + match &operations[1] { + Composite::Train2 { f, g } => { + match &**f { + Composite::SelectionFunction { + func: BuiltinSelectionFn::CharOffset(5), + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + match &**g { + Composite::SelectionFunction { + func: BuiltinSelectionFn::Span, + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + } + _ => panic!(), + } + match &operations[2] { + Composite::TextFunction { + func: BuiltinTextFn::Literal(s), + swapped: false, + } => { + assert_eq!(s, "tr"); + } + _ => panic!(), + } + } + _ => panic!(), + } + } + + #[test] + fn test_parse_nested_groups_in_train() { + let input = r#"{e<#5, ;$ i} (~e<d) $"#; + let tokens = tokenise(input).unwrap(); + let result = parse(tokens).unwrap(); + match result { + Composite::Train3 { f, g, h } => { + match f.as_ref() { + Composite::Group { operations } => { + assert_eq!(operations.len(), 2); + match &operations[0] { + Composite::Hook { + kind: HookKind::After, + left, + right, + } => { + match left.as_ref() { + Composite::SelectionFunction { + func: BuiltinSelectionFn::Empty, + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + match right.as_ref() { + Composite::SelectionFunction { + func: BuiltinSelectionFn::CharOffset(5), + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + } + _ => panic!(), + } + match &operations[1] { + Composite::Train2 { f: f2, g: g2 } => { + match f2.as_ref() { + Composite::SelectionFunction { + func: BuiltinSelectionFn::EndOfBuffer, + search_mod: Some(SearchModifier::Sequential), + result_transform: None, + } => {} + _ => panic!(), + } + match g2.as_ref() { + Composite::TextFunction { + func: BuiltinTextFn::Insert, + swapped: false, + } => {} + _ => panic!(), + } + } + _ => panic!(), + } + } + _ => panic!(), + } + match g.as_ref() { + Composite::Hook { + kind: HookKind::After, + left, + right, + } => { + match left.as_ref() { + Composite::SelectionFunction { + func: BuiltinSelectionFn::Empty, + search_mod: None, + result_transform: Some(ResultTransform::Complement), + } => {} + _ => panic!(), + } + match right.as_ref() { + Composite::TextFunction { + func: BuiltinTextFn::Delete, + swapped: false, + } => {} + _ => panic!(), + } + } + _ => panic!(), + } + match h.as_ref() { + Composite::SelectionFunction { + func: BuiltinSelectionFn::EndOfBuffer, + search_mod: None, + result_transform: None, + } => {} + _ => panic!(), + } + } + _ => panic!(), + } + } } |
