From 05e1cc51b2fb0824580925b55319377305105c44 Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Sun, 28 Jan 2024 23:40:30 +0100 Subject: feat(zipper): add option to not move past last element when moving right --- lib/zipper.ml | 24 +++++++++++++++--------- lib/zipper.mli | 26 ++++++++++++++++---------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/lib/zipper.ml b/lib/zipper.ml index 5f3b37a..6077f21 100644 --- a/lib/zipper.ml +++ b/lib/zipper.ml @@ -1,4 +1,4 @@ -(* Module [Zipper]: functional zippers *) +(* Module [Zipper]: functional linear zippers *) open Base @@ -11,7 +11,10 @@ let after z = z.after let focus z = after z |> Sequence.next |> Option.map ~f:fst let focus_or ~default z = Option.value ~default (focus z) let is_far_left z = before z |> Sequence.is_empty -let is_far_right z = after z |> Sequence.is_empty + +let is_far_right ?(by_one = false) z = + after z |> Sequence.length_is_bounded_by ~max:(if by_one then 1 else 0) + let is_empty z = is_far_left z && is_far_right z let left_length z = z.pos let right_length z = after z |> Sequence.length @@ -30,23 +33,26 @@ let rec left_while f z = let rec far_left z = if is_far_left z then z else z |> left |> far_left -let right z = +let right ?(by_one = false) z = match Sequence.next z.after with | None -> z + | Some (_, t) when by_one && Sequence.is_empty t -> z | Some (h, t) -> { pos = z.pos + 1; before = Sequence.shift_right z.before h; after = t } -let rec right_while f z = +let rec right_while ?(by_one = false) f z = if - (not (is_far_right z)) && Option.(focus z |> map ~f |> value ~default:false) - then right z |> right_while f + (not (is_far_right ~by_one z)) + && Option.(focus z |> map ~f |> value ~default:false) + then right z |> right_while ~by_one f else z -let rec far_right z = if is_far_right z then z else z |> right |> far_right +let rec far_right ?(by_one = false) z = + if is_far_right ~by_one z then z else z |> right |> far_right ~by_one -let goto n z = +let goto ?(by_one = false) n z = let n = n - z.pos in - let step = if n < 0 then left else right in + let step = if n < 0 then left else right ~by_one in Fn.apply_n_times ~n:(abs n) step z let pop ?(n = 1) z = diff --git a/lib/zipper.mli b/lib/zipper.mli index a300b12..0481cfe 100644 --- a/lib/zipper.mli +++ b/lib/zipper.mli @@ -46,8 +46,10 @@ val focus_or : default:'a -> 'a zipper -> 'a val is_far_left : 'a zipper -> bool (** Return whether the cursor is at the beginning of the zipper. *) -val is_far_right : 'a zipper -> bool -(** Return whether the cursor is at the end of the zipper. *) +val is_far_right : ?by_one:bool -> 'a zipper -> bool +(** Return whether the cursor is at the end of the zipper. + If [by_one] is [true], the cursor is considered at the far right + even when pointing {b to} the last element of the sequence. *) val is_empty : 'a zipper -> bool (** Return whether the zipper is empty. *) @@ -79,26 +81,30 @@ val left_while : ('a -> bool) -> 'a zipper -> 'a zipper val far_left : 'a zipper -> 'a zipper (** Move the cursor to the left, as much as possible. *) -val right : 'a zipper -> 'a zipper -(** Move the cursor one step to the right, if possible. +val right : ?by_one:bool -> 'a zipper -> 'a zipper +(** Move the cursor one step to the right, if possible. If [by_one] is + [true], the cursor won't move past the last element. Calling [right z], - if [z] is [([3; 2; 1], [4; 5])], the result is [([4; 3; 2; 1], [5])], - if [z] is [([1; 2; 3], [])], the result is [([1; 2; 3], [])]. *) -val right_while : ('a -> bool) -> 'a zipper -> 'a zipper +val right_while : ?by_one:bool -> ('a -> bool) -> 'a zipper -> 'a zipper (** [right_while f z] moves the cursor in [z] to the right as long as the predicate [f] is [true] when applied to the focus, or the right - end of the zipper is reached. *) + end of the zipper is reached. If [by_one] is [true], the cursor won't + move past the last element. *) -val far_right : 'a zipper -> 'a zipper -(** Move the cursor to the right, as much as possible. *) +val far_right : ?by_one:bool -> 'a zipper -> 'a zipper +(** Move the cursor to the right, as much as possible. If [by_one] is + [true], the cursor won't move past the last element.*) -val goto : int -> 'a zipper -> 'a zipper +val goto : ?by_one:bool -> int -> 'a zipper -> 'a zipper (** Move the cursor to a specific (absolute) position in the zipper. Depending on the current position, it either moves the cursor - forward or backwards, without crossing the zipper boundaries. *) + forward or backwards, without crossing the zipper boundaries. If + [by_one] is [true], the cursor won't move past the last element. *) (** {1 Changes at the cursor} *) -- cgit v1.2.3