Skip to content

Commit 3849f18

Browse files
authored
Merge pull request #2 from moonbit-community/use-list
Use list
2 parents e4b6f2b + b79ef84 commit 3849f18

File tree

3 files changed

+104
-94
lines changed

3 files changed

+104
-94
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v4
1414
- run: |
15-
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
15+
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s 'pre-release'
1616
echo "$HOME/.moon/bin" >> $GITHUB_PATH
1717
- name: moon check
1818
run: moon check --deny-warn

additional_coverage_test.mbt

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,32 @@ test "comprehensive edge case coverage" {
55
// Test various edge cases that might trigger internal functions
66

77
// Test with empty string operations
8-
let empty_zipper = StringZipper::of_string("")
8+
let empty_zipper = @string_zipper.StringZipper::of_string("")
99
let (squashed, str) = empty_zipper.squash()
1010
inspect(str, content="")
1111
inspect(squashed.offset(), content="0")
1212

1313
// Test drop_until on empty zippers
14-
let empty_zipper2 = StringZipper::of_string("")
14+
let empty_zipper2 = @string_zipper.StringZipper::of_string("")
1515
let result = empty_zipper.drop_until(empty_zipper2)
1616
inspect(result.to_string(), content="")
1717

1818
// Test operations at very end of document
1919
let text = "a"
20-
let zipper = StringZipper::of_string(text)
20+
let zipper = @string_zipper.StringZipper::of_string(text)
2121
let end_zipper = zipper.goto_end()
2222

2323
// Try operations at end that might hit internal edge cases
2424
let buffer = StringBuilder::new()
25-
StringZipper::add_buffer_between(end_zipper, buffer, end_zipper)
25+
@string_zipper.StringZipper::add_buffer_between(
26+
end_zipper, buffer, end_zipper,
27+
)
2628
inspect(buffer.to_string(), content="a")
2729

2830
// Test with single character strings and various positions
29-
let single_char = StringZipper::of_string("x")
30-
let pos0 = single_char.goto_position(Position::new(0, 0))
31-
let pos1 = single_char.goto_position(Position::new(0, 1))
31+
let single_char = @string_zipper.StringZipper::of_string("x")
32+
let pos0 = single_char.goto_position(@string_zipper.Position::new(0, 0))
33+
let pos1 = single_char.goto_position(@string_zipper.Position::new(0, 1))
3234

3335
// Test drop_until from pos0 to pos1
3436
let result_drop = pos0.drop_until(pos1)
@@ -39,7 +41,7 @@ test "comprehensive edge case coverage" {
3941
test "complex multiline scenarios" {
4042
// Test complex scenarios that might hit edge cases
4143
let text = "a\nb\nc"
42-
let zipper = StringZipper::of_string(text)
44+
let zipper = @string_zipper.StringZipper::of_string(text)
4345

4446
// Navigate to various positions and test operations
4547
let line2 = zipper.goto_line(2)
@@ -57,7 +59,7 @@ test "complex multiline scenarios" {
5759
)
5860

5961
// Test large position jumps that might trigger edge cases
60-
let large_pos = zipper.goto_position(Position::new(10, 50)) // Beyond document
62+
let large_pos = zipper.goto_position(@string_zipper.Position::new(10, 50)) // Beyond document
6163
inspect(
6264
large_pos.to_string(),
6365
content=(
@@ -72,12 +74,12 @@ test "complex multiline scenarios" {
7274
test "string view operations edge cases" {
7375
// Test scenarios that might exercise the internal view functions
7476
let text = "hello world test"
75-
let zipper = StringZipper::of_string(text)
77+
let zipper = @string_zipper.StringZipper::of_string(text)
7678

7779
// Insert at various positions to create complex internal structure
78-
let pos5 = zipper.goto_position(Position::new(0, 5))
80+
let pos5 = zipper.goto_position(@string_zipper.Position::new(0, 5))
7981
let inserted1 = pos5.insert(" INSERTED")
80-
let pos10 = inserted1.goto_position(Position::new(0, 10))
82+
let pos10 = inserted1.goto_position(@string_zipper.Position::new(0, 10))
8183
let inserted2 = pos10.insert(" MORE")
8284
inspect(inserted2.to_string(), content="hello INSE MORERTED world test")
8385

@@ -90,7 +92,7 @@ test "string view operations edge cases" {
9092
test "boundary navigation edge cases" {
9193
// Test navigation that might hit internal edge cases
9294
let text = "line1\nline2\nline3\n"
93-
let zipper = StringZipper::of_string(text)
95+
let zipper = @string_zipper.StringZipper::of_string(text)
9496

9597
// Navigate to end and try to go further
9698
let at_end = zipper.goto_end()
@@ -106,7 +108,10 @@ test "boundary navigation edge cases" {
106108
)
107109

108110
// Test apply_change that might create edge case scenarios
109-
let range = Range::new(Position::new(1, 0), Position::new(2, 5))
111+
let range = @string_zipper.Range::new(
112+
@string_zipper.Position::new(1, 0),
113+
@string_zipper.Position::new(2, 5),
114+
)
110115
let changed = zipper.apply_change(range, replacement="REPLACED")
111116
inspect(
112117
changed.to_string(),

string_zipper.mbt

Lines changed: 84 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,25 @@
77
///|
88
/// The main zipper data structure
99
pub struct StringZipper {
10-
priv left : Array[@string.View] // string views to the left of cursor
10+
priv left : @list.List[@string.View] // string views to the left of cursor
1111
priv rel_pos : Int // cursor position within current string view
1212
priv abs_pos : Int // total length of strings in left
1313
priv current : @string.View // current string view containing cursor
1414
priv line : Int // number of '\n' characters traversed
15-
priv right : Array[@string.View] // string views to the right of cursor
15+
priv right : @list.List[@string.View] // string views to the right of cursor
1616
}
1717

1818
///|
1919
/// Creates a string zipper from a string
2020
pub fn StringZipper::of_string(s : String) -> StringZipper {
21-
{ left: [], rel_pos: 0, abs_pos: 0, current: s.view(), right: [], line: 0 }
21+
{
22+
left: @list.empty(),
23+
rel_pos: 0,
24+
abs_pos: 0,
25+
current: s.view(),
26+
right: @list.empty(),
27+
line: 0,
28+
}
2229
}
2330

2431
///|
@@ -39,19 +46,18 @@ fn to_string_and_pos(self : StringZipper) -> (String, Int, Int) {
3946
// Use StringBuilder for efficient string building
4047
let builder = StringBuilder::new()
4148

42-
// Add left string views in reverse order
43-
for i = self.left.length() - 1; i >= 0; i = i - 1 {
44-
builder.write_string(self.left[i].to_string())
49+
// Add left string views in reverse order (since they were prepended)
50+
let left_array = self.left.to_array()
51+
for i = left_array.length() - 1; i >= 0; i = i - 1 {
52+
builder.write_string(left_array[i].to_string())
4553
}
4654
let final_pos = builder.to_string().length() + self.rel_pos
4755

4856
// Add current string view
4957
builder.write_string(self.current.to_string())
5058

5159
// Add right string views
52-
for view in self.right {
53-
builder.write_string(view.to_string())
54-
}
60+
self.right.each(fn(view) { builder.write_string(view.to_string()) })
5561
(builder.to_string(), final_pos, self.line)
5662
}
5763

@@ -63,18 +69,15 @@ pub fn to_string(self : StringZipper) -> String {
6369
}
6470

6571
///|
66-
/// Helper function to add string view to array if not empty
67-
fn cons(view : @string.View, arr : Array[@string.View]) -> Array[@string.View] {
72+
/// Helper function to add string view to list if not empty
73+
fn cons(
74+
view : @string.View,
75+
list : @list.List[@string.View],
76+
) -> @list.List[@string.View] {
6877
if view.length() == 0 {
69-
arr
78+
list
7079
} else {
71-
// Use proper Array operations from standard library
72-
let new_arr : Array[@string.View] = Array::new(capacity=arr.length() + 1)
73-
new_arr.push(view)
74-
for elem in arr {
75-
new_arr.push(elem)
76-
}
77-
new_arr
80+
list.add(view)
7881
}
7982
}
8083

@@ -190,21 +193,20 @@ fn find_next_nl(self : StringZipper) -> StringZipper {
190193
match index_from_view(self.current, pos=self.rel_pos, '\n') {
191194
Some(rel_pos) => { ..self, rel_pos, }
192195
None =>
193-
if self.right.length() == 0 {
194-
{ ..self, rel_pos: self.current.length() }
195-
} else {
196-
let current = self.right[0]
197-
let new_right = self.right[1:].to_array()
198-
let abs_pos = self.abs_pos + self.current.length()
199-
let new_zipper = {
200-
..self,
201-
current,
202-
left: cons(self.current, self.left),
203-
right: new_right,
204-
rel_pos: 0,
205-
abs_pos,
196+
match self.right {
197+
Empty => { ..self, rel_pos: self.current.length() }
198+
More(current, tail=new_right) => {
199+
let abs_pos = self.abs_pos + self.current.length()
200+
let new_zipper = {
201+
..self,
202+
current,
203+
left: cons(self.current, self.left),
204+
right: new_right,
205+
rel_pos: 0,
206+
abs_pos,
207+
}
208+
new_zipper.find_next_nl()
206209
}
207-
new_zipper.find_next_nl()
208210
}
209211
}
210212
}
@@ -232,20 +234,19 @@ fn prev_newline(self : StringZipper) -> StringZipper {
232234
match rindex_from_view(self.current, pos=self.rel_pos, '\n') {
233235
Some(rel_pos) => { ..self, rel_pos, line: self.line - 1 }
234236
None =>
235-
if self.left.length() == 0 {
236-
{ ..self, rel_pos: 0 }
237-
} else {
238-
let current = self.left[0]
239-
let new_left = self.left[1:].to_array()
240-
let new_zipper = {
241-
..self,
242-
current,
243-
left: new_left,
244-
rel_pos: current.length(),
245-
abs_pos: self.abs_pos + self.current.length(),
246-
right: cons(self.current, self.right),
237+
match self.left {
238+
Empty => { ..self, rel_pos: 0 }
239+
More(current, tail=new_left) => {
240+
let new_zipper = {
241+
..self,
242+
current,
243+
left: new_left,
244+
rel_pos: current.length(),
245+
abs_pos: self.abs_pos + self.current.length(),
246+
right: cons(self.current, self.right),
247+
}
248+
new_zipper.prev_newline()
247249
}
248-
new_zipper.prev_newline()
249250
}
250251
}
251252
}
@@ -355,9 +356,10 @@ pub fn squash(self : StringZipper) -> (StringZipper, String) {
355356
pub fn to_string_debug(self : StringZipper) -> String {
356357
let builder = StringBuilder::new()
357358

358-
// Add left string views in reverse order
359-
for i = self.left.length() - 1; i >= 0; i = i - 1 {
360-
builder.write_string(self.left[i].to_string())
359+
// Add left string views in reverse order (since they were prepended)
360+
let left_array = self.left.to_array()
361+
for i = left_array.length() - 1; i >= 0; i = i - 1 {
362+
builder.write_string(left_array[i].to_string())
361363
}
362364

363365
// Add current string view up to cursor position
@@ -381,9 +383,7 @@ pub fn to_string_debug(self : StringZipper) -> String {
381383
builder.write_string(suffix)
382384

383385
// Add right string views
384-
for view in self.right {
385-
builder.write_string(view.to_string())
386-
}
386+
self.right.each(fn(view) { builder.write_string(view.to_string()) })
387387
builder.to_string()
388388
}
389389

@@ -408,8 +408,9 @@ pub fn StringZipper::add_buffer_between(
408408
fn StringZipper::is_end_internal(self : StringZipper) -> Bool {
409409
let res = self.current.length() == self.rel_pos
410410
if res {
411-
if self.right.length() > 0 {
412-
abort("invalid state: current = \{self.current.to_string()}")
411+
match self.right {
412+
Empty => ()
413+
_ => abort("invalid state: current = \{self.current.to_string()}")
413414
}
414415
}
415416
res
@@ -418,7 +419,10 @@ fn StringZipper::is_end_internal(self : StringZipper) -> Bool {
418419
///|
419420
/// Checks if cursor is at the beginning of the text (internal helper)
420421
fn StringZipper::is_begin_internal(self : StringZipper) -> Bool {
421-
self.left.length() == 0 && self.rel_pos == 0
422+
match self.left {
423+
Empty => self.rel_pos == 0
424+
_ => false
425+
}
422426
}
423427

424428
///|
@@ -449,18 +453,18 @@ fn StringZipper::advance_char_internal(self : StringZipper) -> StringZipper {
449453
let rel_pos = self.rel_pos + 1
450454
if rel_pos < current_len {
451455
{ ..self, rel_pos, line }
452-
} else if self.right.length() == 0 {
453-
{ ..self, rel_pos, line }
454456
} else {
455-
let current = self.right[0]
456-
let new_right = self.right[1:].to_array()
457-
{
458-
abs_pos: self.abs_pos + self.current.length(),
459-
left: cons(self.current, self.left),
460-
current,
461-
line,
462-
right: new_right,
463-
rel_pos: 0,
457+
match self.right {
458+
Empty => { ..self, rel_pos, line }
459+
More(current, tail=new_right) =>
460+
{
461+
abs_pos: self.abs_pos + self.current.length(),
462+
left: cons(self.current, self.left),
463+
current,
464+
line,
465+
right: new_right,
466+
rel_pos: 0,
467+
}
464468
}
465469
}
466470
}
@@ -496,18 +500,19 @@ fn drop_until_internal(
496500
}
497501
let right = cons(drop_view(until.current, until.rel_pos), until.right)
498502
let left = cons(take_view(from.current, from.rel_pos), from.left)
499-
if right.length() > 0 {
500-
let current = right[0]
501-
let new_right = right[1:].to_array()
502-
let abs_pos = from.abs_pos + from.current.length()
503-
{ ..from, right: new_right, left, abs_pos, current, rel_pos: 0 }
504-
} else if left.length() == 0 {
505-
StringZipper::of_string("")
506-
} else {
507-
let current = left[0]
508-
let new_left = left[1:].to_array()
509-
let rel_pos = current.length()
510-
let abs_pos = from.abs_pos + rel_pos
511-
{ ..from, right, left: new_left, current, rel_pos, abs_pos }
503+
match right {
504+
More(current, tail=new_right) => {
505+
let abs_pos = from.abs_pos + from.current.length()
506+
{ ..from, right: new_right, left, abs_pos, current, rel_pos: 0 }
507+
}
508+
Empty =>
509+
match left {
510+
Empty => StringZipper::of_string("")
511+
More(current, tail=new_left) => {
512+
let rel_pos = current.length()
513+
let abs_pos = from.abs_pos + rel_pos
514+
{ ..from, right, left: new_left, current, rel_pos, abs_pos }
515+
}
516+
}
512517
}
513518
}

0 commit comments

Comments
 (0)