aboutsummaryrefslogtreecommitdiff
path: root/rprt-engine/tests
diff options
context:
space:
mode:
Diffstat (limited to 'rprt-engine/tests')
-rw-r--r--rprt-engine/tests/selection_functions.rs245
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:?}"),
+ }
+}