diff options
Diffstat (limited to 'rprt-engine/tests/selection_functions.rs')
| -rw-r--r-- | rprt-engine/tests/selection_functions.rs | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/rprt-engine/tests/selection_functions.rs b/rprt-engine/tests/selection_functions.rs new file mode 100644 index 0000000..c326375 --- /dev/null +++ b/rprt-engine/tests/selection_functions.rs @@ -0,0 +1,245 @@ +use rprt_engine::{ + evaluate::evaluate, + expression::{BuiltinSelectionFn, ResultTransform, SearchModifier}, + parser::parse, + selection::{Interval, Selection}, + selection_functions::{SelectionFunctionError, evaluate_selection_function}, + state::EditorState, + token::tokenise, +}; + +fn state_with_content(content: &str) -> EditorState { + let mut es = EditorState::new(); + es.create_buffer("test".to_string(), content.to_string(), None); + es +} + +/// Parse `input` and evaluate it against a fresh state holding `content`. +fn eval_str(content: &str, input: &str) -> Result<Selection, String> { + let mut es = state_with_content(content); + let tokens = tokenise(input).map_err(|e| format!("tokenise: {e:?}"))?; + let comp = parse(tokens).map_err(|e| format!("parse: {e:?}"))?; + evaluate(&mut es, comp).map_err(|e| format!("evaluate: {e:?}")) +} + +fn assert_scalar(res: Selection, pos: usize) { + match res { + Selection::Scalar { buffer_id, pos: p } => { + assert_eq!(buffer_id, 0); + assert_eq!(p, pos, "expected scalar at {pos}"); + } + other => panic!("expected scalar at {pos}, got {other:?}"), + } +} + +#[test] +fn niladic_character() { + assert_scalar(eval_str("hello world", "#5").unwrap(), 5); + // clamped to buffer end + assert_scalar(eval_str("hello world", "#100").unwrap(), 11); +} + +#[test] +fn niladic_end_unparameterised() { + // These go through UnparameterisedSelectionFunction + the + // SelectionFunction<()> blanket impl. + assert_scalar(eval_str("hello world", "$").unwrap(), 11); + assert_scalar(eval_str("hello world", "'$").unwrap(), 0); + assert!(eval_str("hello world", ";$").is_err()); + assert!(eval_str("hello world", "';$").is_err()); + assert!(eval_str("hello world", ";'$").is_err()); +} + +#[test] +fn monadic_over_scalar() { + // Train: f = #5 (niladic), g = $ applied to f's result (rank-0 vectorise). + assert_scalar(eval_str("hello world", "#5 $").unwrap(), 5); + // "α ;$" = "End of buffer" + assert_scalar(eval_str("hello world", "#5 ;$").unwrap(), 11); + assert_scalar(eval_str("hello world", "#5 '$").unwrap(), 5); +} + +#[test] +fn character_within_interval() { + // "α #n" rank 1: "Char n within α", offset from the interval's start, + // clamped at its end. + let es = state_with_content("hello world"); + let interval = || Selection::Vector { + buffer_id: 0, + interval: Interval::new(2, 9), + }; + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(3), + None, + None, + Some(interval()), + None, + ) + .unwrap(); + assert_scalar(res, 5); // 2 + 3 + + // clamped at the interval's end + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(100), + None, + None, + Some(interval()), + None, + ) + .unwrap(); + assert_scalar(res, 9); +} + +#[test] +fn search_modifiers_character() { + // ;#1: min(pos + 1, max) + assert_scalar(eval_str("hello world", "#2 ;#1").unwrap(), 3); + // ';#1: pos - 1 + assert_scalar(eval_str("hello world", "#2 ';#1").unwrap(), 1); +} + +#[test] +fn reverse_character() { + // '#3 niladic: "Character 3 from end of buffer" + assert_scalar(eval_str("hello world", "'#3").unwrap(), 8); + + // "α '#n" rank 1: "Char n from end of α" --- offset back from the + // interval's end boundary, clamped at 0. + let es = state_with_content("hello world"); + let interval = || Selection::Vector { + buffer_id: 0, + interval: Interval::new(2, 9), + }; + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(3), + Some(SearchModifier::Reverse), + None, + Some(interval()), + None, + ) + .unwrap(); + assert_scalar(res, 6); // 9 - 3 + + // n larger than the interval's length clamps to 0. + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(10), + Some(SearchModifier::Reverse), + None, + Some(interval()), + None, + ) + .unwrap(); + assert_scalar(res, 0); +} + +#[test] +fn monadic_over_vectors() { + // #100 applied to a rank-2 selection: min(interval.end, 100) per range. + let es = state_with_content("hello world"); + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(100), + None, + None, + Some(Selection::Vectors { + buffer_id: 0, + ranges: vec![Interval::new(2, 4), Interval::new(6, 9)], + }), + None, + ) + .unwrap(); + match res { + Selection::Vectors { buffer_id, ranges } => { + assert_eq!(buffer_id, 0); + assert_eq!(ranges.len(), 2); + assert_eq!((ranges[0].start, ranges[0].end), (4, 5)); + assert_eq!((ranges[1].start, ranges[1].end), (9, 10)); + } + other => panic!("expected Vectors, got {other:?}"), + } +} + +#[test] +fn complement_transform() { + // ~#5 on "hello world": complement of pos 5 is [0..5) and (6..11]. + let es = state_with_content("hello world"); + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::CharOffset(5), + None, + Some(ResultTransform::Complement), + None, + None, + ) + .unwrap(); + match res { + Selection::Vectors { buffer_id, ranges } => { + assert_eq!(buffer_id, 0); + assert_eq!(ranges.len(), 2); + assert_eq!((ranges[0].start, ranges[0].end), (0, 5)); + assert_eq!((ranges[1].start, ranges[1].end), (6, 11)); + } + other => panic!("expected Vectors, got {other:?}"), + } +} + +#[test] +fn empty_selection() { + // `e` and `α e`: empty selection of rank 3. + match eval_str("hello world", "e").unwrap() { + Selection::MultiVectors { multi_ranges } => assert!(multi_ranges.is_empty()), + other => panic!("expected rank-3 empty, got {other:?}"), + } + match eval_str("hello world", "#5 e").unwrap() { + Selection::MultiVectors { multi_ranges } => assert!(multi_ranges.is_empty()), + other => panic!("expected rank-3 empty, got {other:?}"), + } + + // `'e` and `;e`: empty selection of rank 2, anchored to the buffer. + match eval_str("hello world", "#5 'e").unwrap() { + Selection::Vectors { buffer_id, ranges } => { + assert_eq!(buffer_id, 0); + assert!(ranges.is_empty()); + } + other => panic!("expected rank-2 empty, got {other:?}"), + } + match eval_str("hello world", "#5 ;e").unwrap() { + Selection::Vectors { buffer_id, ranges } => { + assert_eq!(buffer_id, 0); + assert!(ranges.is_empty()); + } + other => panic!("expected rank-2 empty, got {other:?}"), + } + + // `;e` and friends have no niladic form. + assert!(eval_str("hello world", ";e").is_err()); + assert!(eval_str("hello world", ";'e").is_err()); + assert!(eval_str("hello world", "';e").is_err()); +} + +#[test] +fn dyadic_is_rejected() { + let es = state_with_content("hello world"); + let scalar = || Selection::Scalar { + buffer_id: 0, + pos: 0, + }; + let res = evaluate_selection_function( + &es, + BuiltinSelectionFn::EndOfBuffer, + None, + None, + Some(scalar()), + Some(scalar()), + ); + match res { + Err(SelectionFunctionError::NoDyadicForm(name)) => { + assert_eq!(name, "end"); + } + other => panic!("expected NoDyadicForm, got {other:?}"), + } +} |
