diff options
Diffstat (limited to 'rprt-engine')
| -rw-r--r-- | rprt-engine/src/evaluate.rs | 10 | ||||
| -rw-r--r-- | rprt-engine/src/selection.rs | 138 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/function_character.rs | 298 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/function_empty.rs | 175 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/function_end.rs | 204 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/mod.rs | 31 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/result_transformation.rs | 47 | ||||
| -rw-r--r-- | rprt-engine/src/selection_functions/types.rs | 237 | ||||
| -rw-r--r-- | rprt-engine/tests/selection_functions.rs | 245 |
9 files changed, 1086 insertions, 299 deletions
diff --git a/rprt-engine/src/evaluate.rs b/rprt-engine/src/evaluate.rs index 4f567fb..eeb1415 100644 --- a/rprt-engine/src/evaluate.rs +++ b/rprt-engine/src/evaluate.rs @@ -1,7 +1,7 @@ use crate::{ expression::{Composite, HookKind}, - selection::{Selection, SelectionError, VectoriseError}, - selection_functions::{evaluate_selection_function, SFError}, + selection::Selection, + selection_functions::{SelectionFunctionError, evaluate_selection_function}, state::{EditorState, GroupedChangeError, StateChange, StateResult}, text::Text, }; @@ -11,9 +11,7 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum EvaluationError { #[error("{0}")] - SelectionFunctionError(VectoriseError<SFError>), - #[error("{0}")] - SelectionUnionError(SelectionError), + SelectionFunctionError(SelectionFunctionError), #[error("Error in processing group {0}")] GroupError(GroupedChangeError), #[error("Invalid right-only application")] @@ -152,7 +150,7 @@ fn _eval( } .map_err(EvaluationError::GroupError)?; - let sel = Selection::union(selections).map_err(EvaluationError::SelectionUnionError)?; + let sel = Selection::union(selections); Ok((sel, states)) } } diff --git a/rprt-engine/src/selection.rs b/rprt-engine/src/selection.rs index a3259b8..45a895b 100644 --- a/rprt-engine/src/selection.rs +++ b/rprt-engine/src/selection.rs @@ -1,4 +1,5 @@ use crate::buffer::BufferID; +use crate::state::EditorState; use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Display; @@ -69,26 +70,55 @@ pub enum Selection { }, } -// rust is ... sigh. I have to make a wrapper class and worse still _other -// people_ have to deal with my wrapper class! -#[derive(Error, Debug)] -pub enum VectoriseError<E> -where - E: std::error::Error + 'static, -{ - #[error("An error occurred during processing: {0}")] - ProcessingError(E), - - #[error("A selection error occurred: {0}")] - SelectionError(SelectionError), -} +/// A type that knows how to act on the rank-0 (scalar) and rank-1 (interval) +/// elements of a [`Selection`], and so can be vectorised over one. +pub trait Vectorisable<P, E> { + /// Action on a rank-0 element. + fn rank0( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, E>; -impl<E> From<E> for VectoriseError<E> -where - E: std::error::Error + 'static, -{ - fn from(err: E) -> Self { - VectoriseError::ProcessingError(err) + /// Action on a rank-1 element. + fn rank1( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, E>; + + /// Vectorise `sel`: apply `rank0`/`rank1` to every element and union + /// the results. + fn vectorise(&self, param: &P, es: &EditorState, sel: &Selection) -> Result<Selection, E> { + // Single rank-0/rank-1 elements pass through as-is (no rank + // promotion); multi-element selections are unioned. + match sel { + Selection::Scalar { buffer_id, pos } => Self::rank0(self, param, es, *buffer_id, *pos), + Selection::Vector { + buffer_id, + interval, + } => Self::rank1(self, param, es, *buffer_id, interval), + Selection::Vectors { buffer_id, ranges } => { + let mut selections = Vec::with_capacity(ranges.len()); + for interval in ranges { + selections.push(Self::rank1(self, param, es, *buffer_id, interval)?); + } + Ok(Selection::union(selections)) + } + Selection::MultiVectors { multi_ranges } => { + let mut selections = Vec::new(); + for (buffer_id, ranges) in multi_ranges { + for interval in ranges { + selections.push(Self::rank1(self, param, es, *buffer_id, interval)?); + } + } + Ok(Selection::union(selections)) + } + } } } @@ -210,15 +240,20 @@ impl Selection { } } - pub fn union(selections: Vec<Self>) -> Result<Self, SelectionError> { + /// Union the given selections into a single selection. + /// + /// The target rank is the maximum rank needed to hold all inputs (rank 3 + /// if they span multiple buffers), to which every input can be promoted, + /// so this operation cannot fail. + pub fn union(selections: Vec<Self>) -> Self { if selections.is_empty() { - return Ok(Self::empty()); + return Self::empty(); } let all_buffers: HashSet<BufferID> = selections.iter().flat_map(|s| s.buffers()).collect(); if all_buffers.is_empty() { - return Ok(Self::empty()); + return Self::empty(); } let max_rank = if all_buffers.len() > 1 { @@ -232,9 +267,13 @@ impl Selection { .max(Rank::Two) // At least rank 2 }; - let promoted: Result<Vec<Self>, SelectionError> = - selections.iter().map(|s| s.promote(max_rank)).collect(); - let promoted = promoted?; + let promoted: Vec<Self> = selections + .iter() + .map(|s| { + s.promote(max_rank) + .expect("union promotes to the max rank present, which cannot fail") + }) + .collect(); match max_rank { Rank::Two => { @@ -248,10 +287,10 @@ impl Selection { } } - Ok(Self::Vectors { + Self::Vectors { ranges: all_intervals, buffer_id, - }) + } } Rank::Three => { let mut buffer_intervals: HashMap<BufferID, Vec<Interval>> = @@ -273,52 +312,11 @@ impl Selection { } } - Ok(Self::MultiVectors { + Self::MultiVectors { multi_ranges: buffer_intervals, - }) + } } _ => unreachable!(), } } - - pub fn vectorise<S, E>( - &self, - state: &S, - fn_rank_zero: impl Fn(&S, BufferID, usize) -> Result<Selection, E> + 'static, - fn_rank_one: impl Fn(&S, BufferID, &Interval) -> Result<Selection, E> + 'static, - ) -> Result<Selection, VectoriseError<E>> - where - E: std::error::Error + 'static, - { - // look at this mess! - use VectoriseError::{ProcessingError, SelectionError}; - let do_rank_one = |b: usize, rs: &Vec<Interval>| { - rs.iter() - .map(|int| fn_rank_one(state, b, int).map_err(ProcessingError)) - .collect::<Result<_, _>>() - }; - - match self { - &Self::Scalar { buffer_id, pos } => { - fn_rank_zero(state, buffer_id, pos).map_err(ProcessingError) - } - Self::Vector { - interval, - buffer_id, - } => fn_rank_one(state, *buffer_id, interval).map_err(VectoriseError::ProcessingError), - Self::Vectors { buffer_id, ranges } => { - let results = do_rank_one(*buffer_id, ranges)?; - Self::union(results).map_err(SelectionError) - } - Self::MultiVectors { multi_ranges } => { - let all_ok: Vec<Vec<Self>> = multi_ranges - .iter() - .map(|(buffer_id, ranges)| do_rank_one(*buffer_id, ranges)) - .collect::<Result<_, _>>()?; - // It would seem that Rust has no built in monadic flatten, or in general cannot lift things to operate on Result... :( - let results: Vec<Self> = all_ok.into_iter().flatten().collect(); - Self::union(results).map_err(SelectionError) - } - } - } } diff --git a/rprt-engine/src/selection_functions/function_character.rs b/rprt-engine/src/selection_functions/function_character.rs index b4bbf0f..4b41297 100644 --- a/rprt-engine/src/selection_functions/function_character.rs +++ b/rprt-engine/src/selection_functions/function_character.rs @@ -1,91 +1,249 @@ use crate::{ - dispatch_selection_function, - selection::Selection, - selection_function, - selection_functions::types::{SFArguments, SFError, SFResult, get_buffer}, + buffer::BufferID, + expression::SearchModifier, + selection::{Interval, Selection}, + selection_functions::types::{SelectionFunction, SelectionFunctionError, get_buffer}, state::EditorState, }; -selection_function! { - name: character, - param_type: usize, - niladic: |(param, es): (usize, &EditorState)| { +pub struct Character; + +impl SelectionFunction<usize> for Character { + const NAME: &'static str = "character"; + + fn niladic( + &self, + param: &usize, + es: &EditorState, + ) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, es.current_buffer_id)?; Ok(Selection::Scalar { buffer_id: es.current_buffer_id, - pos: usize::min(param, buf.max_pos()) + pos: usize::min(*param, buf.max_pos()), + }) + } - }) }, - monadic_rank0: |_, buffer_id, pos| Ok(Selection::Scalar {buffer_id, pos}), - monadic_rank1: |(param, _), buffer_id, int| Ok( - Selection::Scalar {buffer_id, pos: usize::min(int.end, *param)} - ) + fn monadic_rank0( + &self, + _param: &usize, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { buffer_id, pos }) + } + + fn monadic_rank1( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: usize::min(interval.start + *param, interval.end), + }) + } } -selection_function!( - name: sequential_character, - param_type: usize, - niladic: |_| Err(SFError::NoNiladicForm(";n").into()), - monadic_rank0: |(param, es), buffer_id, pos| { +pub struct SequentialCharacter; + +impl SelectionFunction<usize> for SequentialCharacter { + const NAME: &'static str = "sequential_character"; + + fn niladic( + &self, + _param: &usize, + _es: &EditorState, + ) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";n")) + } + + fn monadic_rank0( + &self, + param: &usize, + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, buffer_id)?; - Ok(Selection::Scalar {buffer_id, pos: usize::min(pos+*param, buf.max_pos())}) - }, - monadic_rank1: |(param, es), buffer_id, int| { + Ok(Selection::Scalar { + buffer_id, + pos: usize::min(pos + *param, buf.max_pos()), + }) + } + + fn monadic_rank1( + &self, + param: &usize, + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, buffer_id)?; - Ok(Selection::Scalar {buffer_id, pos: usize::min(int.end+*param, buf.max_pos())}) + Ok(Selection::Scalar { + buffer_id, + pos: usize::min(interval.end + *param, buf.max_pos()), + }) } -); +} + +pub struct ReverseCharacter; + +impl SelectionFunction<usize> for ReverseCharacter { + const NAME: &'static str = "reverse_character"; -selection_function!( - name: reverse_character, - param_type: usize, - niladic: |(param, es): (usize, &EditorState)| { + fn niladic( + &self, + param: &usize, + es: &EditorState, + ) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, es.current_buffer_id)?; Ok(Selection::Scalar { buffer_id: es.current_buffer_id, - pos: if buf.max_pos() < param { + pos: if buf.max_pos() < *param { + 0 + } else { + buf.max_pos() - *param + }, + }) + } + + fn monadic_rank0( + &self, + _param: &usize, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { buffer_id, pos }) + } + + fn monadic_rank1( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: if *param < interval.end { + interval.end - *param + } else { + 0 + }, + }) + } +} + +pub struct SequentialReverseCharacter; + +impl SelectionFunction<usize> for SequentialReverseCharacter { + const NAME: &'static str = "sequential_reverse_character"; + + fn niladic( + &self, + _param: &usize, + _es: &EditorState, + ) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";'n")) + } + + fn monadic_rank0( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: if *param < pos { pos - *param } else { 0 }, + }) + } + + fn monadic_rank1( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: if *param < interval.end { + interval.end - *param + } else { 0 + }, + }) + } +} + +pub struct ReverseSequentialCharacter; + +impl SelectionFunction<usize> for ReverseSequentialCharacter { + const NAME: &'static str = "reverse_sequential_character"; + + fn niladic( + &self, + _param: &usize, + _es: &EditorState, + ) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm("';n")) + } + + fn monadic_rank0( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: if *param < pos { pos - *param } else { 0 }, + }) + } + + fn monadic_rank1( + &self, + param: &usize, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: if *param < interval.start { + interval.start - *param } else { - buf.max_pos() - param + 0 }, }) - }, - monadic_rank0: |_, buffer_id, pos| Ok(Selection::Scalar {buffer_id, pos}), - monadic_rank1: |(param, _), buffer_id, int| Ok( - Selection::Scalar {buffer_id, pos: usize::min(int.end, *param)} - ) -); - -selection_function!( - name: sequential_reverse_character, - param_type: usize, - niladic: |_| Err(SFError::NoNiladicForm(";'n").into()), - monadic_rank0: |(param, _), buffer_id, pos| { - Ok(Selection::Scalar {buffer_id, pos: if *param<pos {pos - *param} else {0}}) - }, - monadic_rank1: |(param, _), buffer_id, int| { - Ok(Selection::Scalar {buffer_id, pos: if *param<int.end {int.end - *param} else {0}}) - } -); - -selection_function!( - name: reverse_sequential_character, - param_type: usize, - niladic: |_| Err(SFError::NoNiladicForm("';n").into()), - monadic_rank0: |(param, _), buffer_id, pos| { - Ok(Selection::Scalar {buffer_id, pos: if *param<pos {pos - *param} else {0}}) - }, - monadic_rank1: |(param, _), buffer_id, int| { - Ok(Selection::Scalar {buffer_id, pos: if *param<int.start {int.start - *param} else {0}}) - } -); - -dispatch_selection_function!( - name: selection_function_character, - param_type: usize, - base: character, - sequential: sequential_character, - reverse: reverse_character, - sequential_reverse: sequential_reverse_character, - reverse_sequential: reverse_sequential_character, -); + } +} + +/// Dispatch on the search modifier and apply the matching character-family +/// function to the supplied selections. +pub fn selection_function_character( + offset: usize, + es: &EditorState, + left: Option<&Selection>, + right: Option<&Selection>, + search_mod: Option<SearchModifier>, +) -> Result<Selection, SelectionFunctionError> { + match search_mod { + None => Character.apply(&offset, es, left, right), + Some(SearchModifier::Sequential) => SequentialCharacter.apply(&offset, es, left, right), + Some(SearchModifier::Reverse) => ReverseCharacter.apply(&offset, es, left, right), + Some(SearchModifier::SequentialReverse) => { + SequentialReverseCharacter.apply(&offset, es, left, right) + } + Some(SearchModifier::ReverseSequential) => { + ReverseSequentialCharacter.apply(&offset, es, left, right) + } + } +} diff --git a/rprt-engine/src/selection_functions/function_empty.rs b/rprt-engine/src/selection_functions/function_empty.rs index bf0a45c..4b69dcf 100644 --- a/rprt-engine/src/selection_functions/function_empty.rs +++ b/rprt-engine/src/selection_functions/function_empty.rs @@ -1,12 +1,175 @@ use crate::{ - selection::Selection, - selection_functions::types::{SFArguments, SFResult}, + buffer::BufferID, + expression::SearchModifier, + selection::{Interval, Selection}, + selection_functions::types::{ + SelectionFunction, SelectionFunctionError, UnparameterisedSelectionFunction, + }, + state::EditorState, }; -pub fn empty_sequential(_: SFArguments) -> SFResult { - Ok(Selection::empty()) +pub struct Empty; + +impl UnparameterisedSelectionFunction for Empty { + const NAME: &'static str = "empty"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::empty()) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + _buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::empty()) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + _buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::empty()) + } +} + +pub struct ReverseEmpty; + +impl UnparameterisedSelectionFunction for ReverseEmpty { + const NAME: &'static str = "reverse_empty"; + + fn niladic(&self, es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(es.current_buffer_id)) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } +} + +pub struct SequentialEmpty; + +impl UnparameterisedSelectionFunction for SequentialEmpty { + const NAME: &'static str = "sequential_empty"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";e")) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } +} + +pub struct SequentialReverseEmpty; + +impl UnparameterisedSelectionFunction for SequentialReverseEmpty { + const NAME: &'static str = "sequential_reverse_empty"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";'e")) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } +} + +pub struct ReverseSequentialEmpty; + +impl UnparameterisedSelectionFunction for ReverseSequentialEmpty { + const NAME: &'static str = "reverse_sequential_empty"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm("';e")) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(empty_in_buffer(buffer_id)) + } +} + +fn empty_in_buffer(buffer_id: BufferID) -> Selection { + Selection::Vectors { + buffer_id, + ranges: Vec::new(), + } } -pub fn empty_structural(_: SFArguments) -> SFResult { - Ok(Selection::empty()) +pub fn selection_function_empty( + es: &EditorState, + left: Option<&Selection>, + right: Option<&Selection>, + search_mod: Option<SearchModifier>, +) -> Result<Selection, SelectionFunctionError> { + match search_mod { + None => Empty.apply(&(), es, left, right), + Some(SearchModifier::Sequential) => SequentialEmpty.apply(&(), es, left, right), + Some(SearchModifier::Reverse) => ReverseEmpty.apply(&(), es, left, right), + Some(SearchModifier::SequentialReverse) => { + SequentialReverseEmpty.apply(&(), es, left, right) + } + Some(SearchModifier::ReverseSequential) => { + ReverseSequentialEmpty.apply(&(), es, left, right) + } + } } diff --git a/rprt-engine/src/selection_functions/function_end.rs b/rprt-engine/src/selection_functions/function_end.rs index 75b4c96..ad161d2 100644 --- a/rprt-engine/src/selection_functions/function_end.rs +++ b/rprt-engine/src/selection_functions/function_end.rs @@ -1,57 +1,142 @@ use crate::{ - dispatch_selection_function, - selection::Selection, - selection_function, - selection_functions::types::{SFArguments, SFError, SFResult, get_buffer}, + buffer::BufferID, + expression::SearchModifier, + selection::{Interval, Selection}, + selection_functions::types::{ + SelectionFunction, SelectionFunctionError, UnparameterisedSelectionFunction, get_buffer, + }, state::EditorState, }; -selection_function! { - name: end, - param_type: (), - niladic: |(_, es): ((), &EditorState)| { +pub struct End; + +impl UnparameterisedSelectionFunction for End { + const NAME: &'static str = "end"; + + fn niladic(&self, es: &EditorState) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, es.current_buffer_id)?; Ok(Selection::Scalar { buffer_id: es.current_buffer_id, pos: buf.max_pos(), }) - }, - monadic_rank0: |_, buffer_id, pos | Ok(Selection::Scalar { buffer_id, pos }), - monadic_rank1: |_, buffer_id, int | Ok(Selection::Scalar { buffer_id, pos: int.end }) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { buffer_id, pos }) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: interval.end, + }) + } } -selection_function! { - name: reverse_end, - param_type: (), - niladic: |(_, es): ((), &EditorState)| { +pub struct ReverseEnd; + +impl UnparameterisedSelectionFunction for ReverseEnd { + const NAME: &'static str = "reverse_end"; + + fn niladic(&self, es: &EditorState) -> Result<Selection, SelectionFunctionError> { Ok(Selection::Scalar { buffer_id: es.current_buffer_id, pos: 0, }) - }, - monadic_rank0: |_, buffer_id, pos | Ok(Selection::Scalar { buffer_id, pos }), - monadic_rank1: |_, buffer_id, int | Ok(Selection::Scalar { buffer_id, pos: int.start }) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { buffer_id, pos }) + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + Ok(Selection::Scalar { + buffer_id, + pos: interval.start, + }) + } } -selection_function! { - name: sequential_end, - param_type: (), - niladic: |_| Err(SFError::NoNiladicForm(";$").into()), - monadic_rank0: |_, buffer_id, pos| Ok(Selection::Scalar { buffer_id, pos }), - monadic_rank1: |(_, es), buffer_id, _| { +pub struct SequentialEnd; + +impl UnparameterisedSelectionFunction for SequentialEnd { + const NAME: &'static str = "sequential_end"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";$")) + } + + fn monadic_rank0( + &self, + es: &EditorState, + buffer_id: BufferID, + _pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + let buf = get_buffer(es, buffer_id)?; + Ok(Selection::Scalar { + buffer_id, + pos: buf.max_pos(), + }) + } + + fn monadic_rank1( + &self, + es: &EditorState, + buffer_id: BufferID, + _interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { let buf = get_buffer(es, buffer_id)?; - Ok(Selection::Scalar { buffer_id, pos: buf.max_pos() }) + Ok(Selection::Scalar { + buffer_id, + pos: buf.max_pos(), + }) } } -selection_function! { - name: sequential_reverse_end, - param_type: (), - niladic: |_| Err(SFError::NoNiladicForm(";'$").into()), - monadic_rank0: |_, buffer_id, pos| { +pub struct SequentialReverseEnd; + +impl UnparameterisedSelectionFunction for SequentialReverseEnd { + const NAME: &'static str = "sequential_reverse_end"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(";'$")) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { Ok(Selection::Scalar { buffer_id, pos }) - }, - monadic_rank1: |_, buffer_id, interval| { + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { Ok(Selection::Scalar { buffer_id, pos: interval.end, @@ -59,14 +144,30 @@ selection_function! { } } -selection_function! { - name: reverse_sequential_end, - param_type: (), - niladic: |_| Err(SFError::NoNiladicForm("';$").into()), - monadic_rank0: |_, buffer_id, pos| { +pub struct ReverseSequentialEnd; + +impl UnparameterisedSelectionFunction for ReverseSequentialEnd { + const NAME: &'static str = "reverse_sequential_end"; + + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm("';$")) + } + + fn monadic_rank0( + &self, + _es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { Ok(Selection::Scalar { buffer_id, pos }) - }, - monadic_rank1: |_, buffer_id, interval| { + } + + fn monadic_rank1( + &self, + _es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { Ok(Selection::Scalar { buffer_id, pos: interval.end, @@ -74,12 +175,19 @@ selection_function! { } } -dispatch_selection_function!( - name: selection_function_end, - param_type: (), - base: end, - sequential: sequential_end, - reverse: reverse_end, - sequential_reverse: sequential_reverse_end, - reverse_sequential: reverse_sequential_end, -); +/// Dispatch on the search modifier and apply the matching end-family +/// function to the supplied selections. +pub fn selection_function_end( + es: &EditorState, + left: Option<&Selection>, + right: Option<&Selection>, + search_mod: Option<SearchModifier>, +) -> Result<Selection, SelectionFunctionError> { + match search_mod { + None => End.apply(&(), es, left, right), + Some(SearchModifier::Sequential) => SequentialEnd.apply(&(), es, left, right), + Some(SearchModifier::Reverse) => ReverseEnd.apply(&(), es, left, right), + Some(SearchModifier::SequentialReverse) => SequentialReverseEnd.apply(&(), es, left, right), + Some(SearchModifier::ReverseSequential) => ReverseSequentialEnd.apply(&(), es, left, right), + } +} diff --git a/rprt-engine/src/selection_functions/mod.rs b/rprt-engine/src/selection_functions/mod.rs index b753200..85c333e 100644 --- a/rprt-engine/src/selection_functions/mod.rs +++ b/rprt-engine/src/selection_functions/mod.rs @@ -3,9 +3,9 @@ use crate::{ selection::Selection, selection_functions::{ function_character::selection_function_character, + function_empty::selection_function_empty, function_end::selection_function_end, result_transformation::{complement_transform, conditional_transform}, - types::SFArguments, }, state::EditorState, }; @@ -16,7 +16,9 @@ mod function_end; mod result_transformation; mod types; -pub use crate::selection_functions::types::{SFError, SFResult}; +pub use crate::selection_functions::types::{ + SelectionFunction, SelectionFunctionError, UnparameterisedSelectionFunction, +}; pub fn evaluate_selection_function( es: &EditorState, @@ -25,31 +27,30 @@ pub fn evaluate_selection_function( result_transform: Option<ResultTransform>, left: Option<Selection>, right: Option<Selection>, -) -> SFResult { - let sf_args = match (left, right) { - (None, None) => Ok(SFArguments::Niladic { es }), - (Some(left), None) => Ok(SFArguments::Monadic { es, left }), - (Some(left), Some(right)) => Ok(SFArguments::Dyadic { es, left, right }), - _ => Err(SFError::NoLeftArgument), - }?; +) -> Result<Selection, SelectionFunctionError> { let res = match sf { BuiltinSelectionFn::CharOffset(off) => { - selection_function_character(off, &sf_args, search_mod) + selection_function_character(off, es, left.as_ref(), right.as_ref(), search_mod) + } + BuiltinSelectionFn::Empty => { + selection_function_empty(es, left.as_ref(), right.as_ref(), search_mod) + } + BuiltinSelectionFn::EndOfBuffer => { + selection_function_end(es, left.as_ref(), right.as_ref(), search_mod) } - BuiltinSelectionFn::EndOfBuffer => selection_function_end((), &sf_args, search_mod), - x => Err(SFError::NotImplemented(x).into()), + x => Err(SelectionFunctionError::NotImplemented(x)), }?; if let Some(result_transform) = result_transform { match result_transform { ResultTransform::Complement => complement_transform(es, res), - ResultTransform::Conditional => conditional_transform(res, &sf_args), + ResultTransform::Conditional => conditional_transform(res, left.as_ref()), ResultTransform::ConditionalComplement => { let res = complement_transform(es, res)?; - conditional_transform(res, &sf_args) + conditional_transform(res, left.as_ref()) } ResultTransform::ComplementConditional => { - let res = conditional_transform(res, &sf_args)?; + let res = conditional_transform(res, left.as_ref())?; complement_transform(es, res) } } diff --git a/rprt-engine/src/selection_functions/result_transformation.rs b/rprt-engine/src/selection_functions/result_transformation.rs index c150916..b437afd 100644 --- a/rprt-engine/src/selection_functions/result_transformation.rs +++ b/rprt-engine/src/selection_functions/result_transformation.rs @@ -1,16 +1,20 @@ use crate::{ buffer::BufferID, - selection::{Interval, Selection}, - selection_functions::types::{SFArguments, SFError, SFResult}, + selection::{Interval, Selection, Vectorisable}, + selection_functions::types::SelectionFunctionError, state::EditorState, }; -pub fn complement_transform(es: &EditorState, result: Selection) -> SFResult { - fn complement_rank0( +struct Complement; + +impl Vectorisable<(), SelectionFunctionError> for Complement { + fn rank0( + &self, + _param: &(), es: &EditorState, buffer_id: BufferID, pos: usize, - ) -> Result<Selection, SFError> { + ) -> Result<Selection, SelectionFunctionError> { if let Some(buf) = es.get_buffer(buffer_id) { let mut ranges = Vec::new(); if pos > 0 { @@ -22,15 +26,17 @@ pub fn complement_transform(es: &EditorState, result: Selection) -> SFResult { } Ok(Selection::Vectors { buffer_id, ranges }) } else { - Err(SFError::BufferNotFound(buffer_id)) + Err(SelectionFunctionError::BufferNotFound(buffer_id)) } } - fn complement_rank1( + fn rank1( + &self, + _param: &(), es: &EditorState, buffer_id: BufferID, interval: &Interval, - ) -> Result<Selection, SFError> { + ) -> Result<Selection, SelectionFunctionError> { if let Some(buf) = es.get_buffer(buffer_id) { let max_pos = buf.max_pos(); if interval.start == 0 && interval.end >= max_pos { @@ -50,25 +56,24 @@ pub fn complement_transform(es: &EditorState, result: Selection) -> SFResult { } Ok(Selection::Vectors { buffer_id, ranges }) } else { - Err(SFError::BufferNotFound(buffer_id)) + Err(SelectionFunctionError::BufferNotFound(buffer_id)) } } +} - result.vectorise(es, complement_rank0, complement_rank1) +pub fn complement_transform( + es: &EditorState, + result: Selection, +) -> Result<Selection, SelectionFunctionError> { + Complement.vectorise(&(), es, &result) } -pub fn conditional_transform(mut result: Selection, arg: &SFArguments) -> SFResult { +pub fn conditional_transform( + mut result: Selection, + left: Option<&Selection>, +) -> Result<Selection, SelectionFunctionError> { if !result.is_empty() { - Ok(match arg { - // TODO: think about this niladic case? - SFArguments::Niladic { .. } => Selection::empty(), - SFArguments::Monadic { es: _, left } => left.clone(), - SFArguments::Dyadic { - es: _, - left, - right: _, - } => left.clone(), - }) + Ok(left.cloned().unwrap_or_else(Selection::empty)) } else { Ok(result) } diff --git a/rprt-engine/src/selection_functions/types.rs b/rprt-engine/src/selection_functions/types.rs index 6bd94bf..4cd76d3 100644 --- a/rprt-engine/src/selection_functions/types.rs +++ b/rprt-engine/src/selection_functions/types.rs @@ -1,13 +1,13 @@ use crate::{ buffer::{Buffer, BufferID}, expression::BuiltinSelectionFn, - selection::{Selection, VectoriseError}, + selection::{Interval, Selection, Vectorisable}, state::EditorState, }; use thiserror::Error; #[derive(Error, Debug)] -pub enum SFError { +pub enum SelectionFunctionError { #[error("Could not find buffer {0}")] BufferNotFound(BufferID), #[error("Function {0} has no dyadic form")] @@ -20,75 +20,186 @@ pub enum SFError { NotImplemented(BuiltinSelectionFn), } -pub enum SFArguments<'a> { - Niladic { - es: &'a EditorState, - }, - Monadic { - es: &'a EditorState, - left: Selection, - }, - Dyadic { - es: &'a EditorState, - left: Selection, - right: Selection, - }, -} +/// A selection function parameterised by `P` (the type of its parameter, +/// e.g. `usize` for character offsets). +/// +/// Each concrete function is a zero-sized struct implementing this trait; +/// the parameter value is passed at call time. The arity of the call +/// (niladic/monadic/dyadic) is routed by [`SelectionFunction::apply`]. +pub trait SelectionFunction<P>: Vectorisable<P, SelectionFunctionError> { + /// Name of the function, used in error messages. + const NAME: &'static str; + + /// The monadic form applied to a rank-0 (scalar) element. + fn monadic_rank0( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError>; + + /// The monadic form applied to a rank-1 (interval) element. + fn monadic_rank1( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError>; + + /// The niladic form. + fn niladic(&self, _param: &P, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(Self::NAME)) + } + + /// The monadic form: vectorise over `left`. + fn monadic( + &self, + param: &P, + es: &EditorState, + left: &Selection, + ) -> Result<Selection, SelectionFunctionError> { + self.vectorise(param, es, left) + } + + /// The dyadic form. + fn dyadic( + &self, + _param: &P, + _es: &EditorState, + _left: &Selection, + _right: &Selection, + ) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoDyadicForm(Self::NAME)) + } -pub type SFResult = Result<Selection, VectoriseError<SFError>>; - -#[macro_export] -macro_rules! selection_function { - ( - name: $name:ident, - param_type: $P:ty, - niladic: $niladic_body:expr, - monadic_rank0: $monadic_rank0_body:expr, - monadic_rank1: $monadic_rank1_body:expr - ) => { - fn $name(param: $P, arg: &SFArguments) -> SFResult { - match arg { - SFArguments::Niladic { es } => ($niladic_body)((param, es)), - SFArguments::Monadic { es, left } => { - left.vectorise(&(param, es), $monadic_rank0_body, $monadic_rank1_body) - } - SFArguments::Dyadic { .. } => Err(SFError::NoDyadicForm(stringify!($name)).into()), - } + /// Route to the arity form matching the selections supplied. + fn apply( + &self, + param: &P, + es: &EditorState, + left: Option<&Selection>, + right: Option<&Selection>, + ) -> Result<Selection, SelectionFunctionError> { + match (left, right) { + (None, None) => self.niladic(param, es), + (Some(left), None) => self.monadic(param, es, left), + (Some(left), Some(right)) => self.dyadic(param, es, left, right), + (None, Some(_)) => Err(SelectionFunctionError::NoLeftArgument), } - }; + } } -#[macro_export] -macro_rules! dispatch_selection_function { - ( - name: $name:ident, - param_type: $P:ty, - base: $bs:ident, - sequential: $sq:ident, - reverse: $rv:ident, - sequential_reverse: $sr:ident, - reverse_sequential: $rs:ident, - ) => { - pub fn $name( - param: $P, - arg: &SFArguments, - search_mod: Option<crate::expression::SearchModifier>, - ) -> SFResult { - match search_mod { - None => ($bs)(param, arg), - Some(crate::expression::SearchModifier::Sequential) => ($sq)(param, arg), - Some(crate::expression::SearchModifier::Reverse) => ($rv)(param, arg), - Some(crate::expression::SearchModifier::SequentialReverse) => ($sr)(param, arg), - Some(crate::expression::SearchModifier::ReverseSequential) => ($rs)(param, arg), - } - } - }; +/// Every selection function can vectorise a selection by applying its +/// monadic rank-0/rank-1 forms to each element. +impl<P, F: SelectionFunction<P> + ?Sized> Vectorisable<P, SelectionFunctionError> for F { + fn rank0( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + self.monadic_rank0(param, es, buffer_id, pos) + } + + fn rank1( + &self, + param: &P, + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + self.monadic_rank1(param, es, buffer_id, interval) + } +} + +/// A selection function with no parameter. +/// +/// Blanket-implements [`SelectionFunction`] with `P = ()`, so +/// unparameterised functions are callable through the same interface. +pub trait UnparameterisedSelectionFunction { + /// Name of the function, used in error messages. + const NAME: &'static str; + + /// The monadic form applied to a rank-0 (scalar) element. + fn monadic_rank0( + &self, + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError>; + + /// The monadic form applied to a rank-1 (interval) element. + fn monadic_rank1( + &self, + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError>; + + /// The niladic form. + fn niladic(&self, _es: &EditorState) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoNiladicForm(Self::NAME)) + } + + /// The dyadic form. + fn dyadic( + &self, + _es: &EditorState, + _left: &Selection, + _right: &Selection, + ) -> Result<Selection, SelectionFunctionError> { + Err(SelectionFunctionError::NoDyadicForm(Self::NAME)) + } +} + +impl<F: UnparameterisedSelectionFunction + ?Sized> SelectionFunction<()> for F { + const NAME: &'static str = <F as UnparameterisedSelectionFunction>::NAME; + + fn monadic_rank0( + &self, + _param: &(), + es: &EditorState, + buffer_id: BufferID, + pos: usize, + ) -> Result<Selection, SelectionFunctionError> { + UnparameterisedSelectionFunction::monadic_rank0(self, es, buffer_id, pos) + } + + fn monadic_rank1( + &self, + _param: &(), + es: &EditorState, + buffer_id: BufferID, + interval: &Interval, + ) -> Result<Selection, SelectionFunctionError> { + UnparameterisedSelectionFunction::monadic_rank1(self, es, buffer_id, interval) + } + + fn niladic(&self, _param: &(), es: &EditorState) -> Result<Selection, SelectionFunctionError> { + UnparameterisedSelectionFunction::niladic(self, es) + } + + fn dyadic( + &self, + _param: &(), + es: &EditorState, + left: &Selection, + right: &Selection, + ) -> Result<Selection, SelectionFunctionError> { + UnparameterisedSelectionFunction::dyadic(self, es, left, right) + } } -pub fn get_buffer(es: &EditorState, buffer_id: BufferID) -> Result<&Buffer, SFError> { +pub fn get_buffer( + es: &EditorState, + buffer_id: BufferID, +) -> Result<&Buffer, SelectionFunctionError> { if let Some(buf) = es.get_buffer(buffer_id) { Ok(buf) } else { - Err(SFError::BufferNotFound(es.current_buffer_id)) + Err(SelectionFunctionError::BufferNotFound(es.current_buffer_id)) } } 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:?}"), + } +} |
