1 ! gap buffer -- Alex Chapman (chapman.alex@gmail.com)
2 ! largely influenced by Strandh and Villeneuve's Flexichain
3 ! for a good introduction see:
4 ! http://p-cos.net/lisp-ecoop/submissions/StrandhVilleneuveMoore.pdf
5 USING: kernel arrays sequences sequences-internals circular math generic ;
8 ! gap-start -- the first element of the gap
9 ! gap-end -- the first element after the gap
10 ! expand-factor -- should be > 1
11 ! min-size -- < 5 is not sensible
19 : required-space ( n gb -- n )
20 tuck gb-expand-factor * ceiling >fixnum swap gb-min-size max ;
23 5 over set-gb-min-size
24 1.5 over set-gb-expand-factor
25 [ >r length r> set-gb-gap-start ] 2keep
26 [ swap length over required-space swap set-gb-gap-end ] 2keep
28 over length over required-space rot { } like resize-array <circular> swap set-delegate
31 M: gb like ( seq gb -- seq ) drop <gb> ;
33 : gap-length ( gb -- n ) [ gb-gap-end ] keep gb-gap-start - ;
35 : buffer-length ( gb -- n ) delegate length ;
37 M: gb length ( gb -- n ) [ buffer-length ] keep gap-length - ;
39 : position>index ( n gb -- n )
40 2dup gb-gap-start >= [
44 : gb@ ( n gb -- n seq ) [ position>index ] keep delegate ;
46 M: gb nth ( n gb -- elt ) bounds-check gb@ nth-unsafe ;
48 M: gb nth-unsafe ( n gb -- elt ) gb@ nth-unsafe ;
50 M: gb set-nth ( elt n seq -- ) bounds-check gb@ set-nth-unsafe ;
52 M: gb set-nth-unsafe ( elt n seq -- ) gb@ set-nth-unsafe ;
54 ! ------------- moving the gap -------------------------------
56 : (copy-element) ( to start seq -- ) tuck nth -rot set-nth ;
58 : copy-element ( dst start seq -- ) >r [ + ] keep r> (copy-element) ;
60 : copy-elements-back ( dst start seq n -- )
62 >r [ copy-element ] 3keep >r 1+ r> r> 1- copy-elements-back
65 : copy-elements-forward ( dst start seq n -- )
67 >r [ copy-element ] 3keep >r 1- r> r> 1- copy-elements-forward
70 : copy-elements ( dst start end seq -- )
72 >r dupd - r> swap copy-elements-forward
74 >r over - r> swap copy-elements-back
77 ! the gap can be moved either forward or back. Moving the gap 'inside' means
78 ! moving elements across the gap. Moving the gap 'around' means changing the
79 ! start of the circular buffer to avoid moving as many elements.
81 ! We decide which method (inside or around) to pick based on the number of
82 ! elements that will need to be moved. We always try to move as few elements as
85 : move-gap? ( i gb -- i gb ? ) 2dup gb-gap-end = not ;
87 : move-gap-forward? ( i gb -- i gb ? ) 2dup gb-gap-start >= ;
89 : move-gap-back-inside? ( i gb -- i gb ? )
90 #! is it cheaper to move the gap inside than around?
91 2dup [ gb-gap-start swap 2 * - ] keep [ buffer-length ] keep gb-gap-end - <= ;
93 : move-gap-forward-inside? ( i gb -- i gb ? )
94 #! is it cheaper to move the gap inside than around?
95 2dup [ gb-gap-end >r 2 * r> - ] keep [ gb-gap-start ] keep buffer-length + <= ;
97 : move-gap-forward-inside ( i gb -- )
98 [ dup gap-length neg swap gb-gap-end rot ] keep delegate copy-elements ;
100 : move-gap-back-inside ( i gb -- )
101 [ dup gap-length swap gb-gap-start 1- rot 1- ] keep delegate copy-elements ;
103 : move-gap-forward-around ( i gb -- )
104 0 over move-gap-back-inside [
106 swap gap-length - neg swap
109 delegate copy-elements
110 ] keep dup gap-length swap delegate change-circular-start ;
112 : move-gap-back-around ( i gb -- )
113 dup buffer-length over move-gap-forward-inside [
116 delegate copy-elements
117 ] keep dup length swap delegate change-circular-start ;
119 : move-gap-forward ( i gb -- )
120 move-gap-forward-inside? [
121 move-gap-forward-inside
123 move-gap-forward-around
126 : move-gap-back ( i gb -- )
127 move-gap-back-inside? [
133 : (move-gap) ( i gb -- )
142 : fix-gap ( n gb -- )
143 2dup [ gap-length + ] keep set-gb-gap-end set-gb-gap-start ;
145 : move-gap ( n gb -- ) 2dup [ position>index ] keep (move-gap) fix-gap ;
147 ! ------------ resizing -------------------------------------
149 : enough-room? ( n gb -- ? )
150 #! is there enough room to add 'n' elements to gb?
151 tuck length + swap buffer-length <= ;
153 : set-new-gap-end ( array gb -- )
154 [ buffer-length swap length swap - ] keep
155 [ gb-gap-end + ] keep set-gb-gap-end ;
157 : after-gap ( gb -- gb )
158 dup delegate swap gb-gap-end tail ;
160 : before-gap ( gb -- gb )
161 dup gb-gap-start head ;
163 : copy-after-gap ( array gb -- )
164 #! copy everything after the gap in 'gb' into the end of 'array',
165 #! and change 'gb's gap-end to reflect the gap-end in 'array'
166 dup after-gap >r 2dup set-new-gap-end gb-gap-end swap r> copy-into ;
168 : copy-before-gap ( array gb -- )
169 #! copy everything before the gap in 'gb' into the start of 'array'
170 before-gap 0 -rot copy-into ; ! gap start doesn't change
172 : resize-buffer ( gb new-size -- )
173 f <array> swap 2dup copy-before-gap 2dup copy-after-gap
174 >r <circular> r> set-delegate ;
176 : decrease-buffer-size ( gb -- )
177 #! the gap is too big, so resize to something sensible
178 dup length over required-space resize-buffer ;
180 : increase-buffer-size ( n gb -- )
181 #! increase the buffer to fit at least 'n' more elements
182 tuck length + over required-space resize-buffer ;
184 : gb-too-big? ( gb -- ? )
185 dup buffer-length over gb-min-size > [
186 dup length over buffer-length rot gb-expand-factor sq / <
189 : maybe-decrease ( gb -- )
194 : ensure-room ( n gb -- )
195 #! ensure that ther will be enough room for 'n' more elements
196 2dup enough-room? [ 2drop ] [
200 ! ------- editing operations ---------------
202 G: insert* ( seq position gb -- ) 2 standard-combination ;
204 : prepare-insert ( seq position gb -- seq gb )
205 tuck move-gap over length over ensure-room ;
207 : insert-elements ( seq gb -- )
208 dup gb-gap-start swap delegate rot copy-into ;
210 : increment-gap-start ( gb n -- )
211 over gb-gap-start + swap set-gb-gap-start ;
213 M: sequence insert* ( seq position gb -- )
214 prepare-insert [ insert-elements ] 2keep swap length increment-gap-start ;
216 M: object insert* ( elem position gb -- ) >r >r 1array r> r> insert* ;
218 : delete* ( position gb -- )
219 tuck move-gap dup gb-gap-end 1+ over set-gb-gap-end maybe-decrease ;
221 ! -------- stack/queue operations -----------
223 : push-start ( obj gb -- ) 0 swap insert* ;
225 : push-end ( obj gb -- ) [ length ] keep insert* ;
227 : pop-elem ( position gb -- elem ) [ nth ] 2keep delete* ;
229 : pop-start ( gb -- elem ) 0 swap pop-elem ;
231 : pop-end ( gb -- elem ) [ length 1- ] keep pop-elem ;
236 [ dup [ pop-end ] keep push-start ]
238 neg [ dup [ pop-start ] keep push-end ]