summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <undyamon@disroot.org>2024-01-28 23:40:30 +0100
committerFederico Igne <undyamon@disroot.org>2024-01-28 23:40:30 +0100
commit05e1cc51b2fb0824580925b55319377305105c44 (patch)
treef1bdc53ba8e312e764e7aa0765e1a6c72dd1c318
parentf46d6661a8f33730c17cceb7e2885e789d6123d8 (diff)
downloadsandy-05e1cc51b2fb0824580925b55319377305105c44.tar.gz
sandy-05e1cc51b2fb0824580925b55319377305105c44.zip
feat(zipper): add option to not move past last element when moving right
-rw-r--r--lib/zipper.ml24
-rw-r--r--lib/zipper.mli26
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 @@
1(* Module [Zipper]: functional zippers *) 1(* Module [Zipper]: functional linear zippers *)
2 2
3open Base 3open Base
4 4
@@ -11,7 +11,10 @@ let after z = z.after
11let focus z = after z |> Sequence.next |> Option.map ~f:fst 11let focus z = after z |> Sequence.next |> Option.map ~f:fst
12let focus_or ~default z = Option.value ~default (focus z) 12let focus_or ~default z = Option.value ~default (focus z)
13let is_far_left z = before z |> Sequence.is_empty 13let is_far_left z = before z |> Sequence.is_empty
14let is_far_right z = after z |> Sequence.is_empty 14
15let is_far_right ?(by_one = false) z =
16 after z |> Sequence.length_is_bounded_by ~max:(if by_one then 1 else 0)
17
15let is_empty z = is_far_left z && is_far_right z 18let is_empty z = is_far_left z && is_far_right z
16let left_length z = z.pos 19let left_length z = z.pos
17let right_length z = after z |> Sequence.length 20let right_length z = after z |> Sequence.length
@@ -30,23 +33,26 @@ let rec left_while f z =
30 33
31let rec far_left z = if is_far_left z then z else z |> left |> far_left 34let rec far_left z = if is_far_left z then z else z |> left |> far_left
32 35
33let right z = 36let right ?(by_one = false) z =
34 match Sequence.next z.after with 37 match Sequence.next z.after with
35 | None -> z 38 | None -> z
39 | Some (_, t) when by_one && Sequence.is_empty t -> z
36 | Some (h, t) -> 40 | Some (h, t) ->
37 { pos = z.pos + 1; before = Sequence.shift_right z.before h; after = t } 41 { pos = z.pos + 1; before = Sequence.shift_right z.before h; after = t }
38 42
39let rec right_while f z = 43let rec right_while ?(by_one = false) f z =
40 if 44 if
41 (not (is_far_right z)) && Option.(focus z |> map ~f |> value ~default:false) 45 (not (is_far_right ~by_one z))
42 then right z |> right_while f 46 && Option.(focus z |> map ~f |> value ~default:false)
47 then right z |> right_while ~by_one f
43 else z 48 else z
44 49
45let rec far_right z = if is_far_right z then z else z |> right |> far_right 50let rec far_right ?(by_one = false) z =
51 if is_far_right ~by_one z then z else z |> right |> far_right ~by_one
46 52
47let goto n z = 53let goto ?(by_one = false) n z =
48 let n = n - z.pos in 54 let n = n - z.pos in
49 let step = if n < 0 then left else right in 55 let step = if n < 0 then left else right ~by_one in
50 Fn.apply_n_times ~n:(abs n) step z 56 Fn.apply_n_times ~n:(abs n) step z
51 57
52let pop ?(n = 1) z = 58let 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
46val is_far_left : 'a zipper -> bool 46val is_far_left : 'a zipper -> bool
47(** Return whether the cursor is at the beginning of the zipper. *) 47(** Return whether the cursor is at the beginning of the zipper. *)
48 48
49val is_far_right : 'a zipper -> bool 49val is_far_right : ?by_one:bool -> 'a zipper -> bool
50(** Return whether the cursor is at the end of the zipper. *) 50(** Return whether the cursor is at the end of the zipper.
51 If [by_one] is [true], the cursor is considered at the far right
52 even when pointing {b to} the last element of the sequence. *)
51 53
52val is_empty : 'a zipper -> bool 54val is_empty : 'a zipper -> bool
53(** Return whether the zipper is empty. *) 55(** Return whether the zipper is empty. *)
@@ -79,26 +81,30 @@ val left_while : ('a -> bool) -> 'a zipper -> 'a zipper
79val far_left : 'a zipper -> 'a zipper 81val far_left : 'a zipper -> 'a zipper
80(** Move the cursor to the left, as much as possible. *) 82(** Move the cursor to the left, as much as possible. *)
81 83
82val right : 'a zipper -> 'a zipper 84val right : ?by_one:bool -> 'a zipper -> 'a zipper
83(** Move the cursor one step to the right, if possible. 85(** Move the cursor one step to the right, if possible. If [by_one] is
86 [true], the cursor won't move past the last element.
84 Calling [right z], 87 Calling [right z],
85 88
86 - if [z] is [([3; 2; 1], [4; 5])], the result is [([4; 3; 2; 1], [5])], 89 - if [z] is [([3; 2; 1], [4; 5])], the result is [([4; 3; 2; 1], [5])],
87 - if [z] is [([1; 2; 3], [])], the result is [([1; 2; 3], [])]. 90 - if [z] is [([1; 2; 3], [])], the result is [([1; 2; 3], [])].
88 *) 91 *)
89 92
90val right_while : ('a -> bool) -> 'a zipper -> 'a zipper 93val right_while : ?by_one:bool -> ('a -> bool) -> 'a zipper -> 'a zipper
91(** [right_while f z] moves the cursor in [z] to the right as long as 94(** [right_while f z] moves the cursor in [z] to the right as long as
92 the predicate [f] is [true] when applied to the focus, or the right 95 the predicate [f] is [true] when applied to the focus, or the right
93 end of the zipper is reached. *) 96 end of the zipper is reached. If [by_one] is [true], the cursor won't
97 move past the last element. *)
94 98
95val far_right : 'a zipper -> 'a zipper 99val far_right : ?by_one:bool -> 'a zipper -> 'a zipper
96(** Move the cursor to the right, as much as possible. *) 100(** Move the cursor to the right, as much as possible. If [by_one] is
101 [true], the cursor won't move past the last element.*)
97 102
98val goto : int -> 'a zipper -> 'a zipper 103val goto : ?by_one:bool -> int -> 'a zipper -> 'a zipper
99(** Move the cursor to a specific (absolute) position in the zipper. 104(** Move the cursor to a specific (absolute) position in the zipper.
100 Depending on the current position, it either moves the cursor 105 Depending on the current position, it either moves the cursor
101 forward or backwards, without crossing the zipper boundaries. *) 106 forward or backwards, without crossing the zipper boundaries. If
107 [by_one] is [true], the cursor won't move past the last element. *)
102 108
103(** {1 Changes at the cursor} *) 109(** {1 Changes at the cursor} *)
104 110