https://github.com/akkartik/mu/blob/master/apps/tile/gap-buffer.mu
  1 type gap-buffer {
  2   left: grapheme-stack
  3   right: grapheme-stack
  4 }
  5 
  6 fn initialize-gap-buffer _self: (addr gap-buffer) {
  7   var self/esi: (addr gap-buffer) <- copy _self
  8   var left/eax: (addr grapheme-stack) <- get self, left
  9   initialize-grapheme-stack left, 0x10  # max-word-size
 10   var right/eax: (addr grapheme-stack) <- get self, right
 11   initialize-grapheme-stack right, 0x10  # max-word-size
 12 }
 13 
 14 # just for tests
 15 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
 16   initialize-gap-buffer self
 17   var stream-storage: (stream byte 0x10)  # max-word-size
 18   var stream/ecx: (addr stream byte) <- address stream-storage
 19   write stream, s
 20   {
 21     var done?/eax: boolean <- stream-empty? stream
 22     compare done?, 0  # false
 23     break-if-!=
 24     var g/eax: grapheme <- read-grapheme stream
 25     add-grapheme-at-gap self, g
 26     loop
 27   }
 28 }
 29 
 30 fn gap-buffer-to-string self: (addr gap-buffer), out: (addr handle array byte) {
 31   var s-storage: (stream byte 0x100)
 32   var s/ecx: (addr stream byte) <- address s-storage
 33   emit-gap-buffer self, s
 34   stream-to-string s, out
 35 }
 36 
 37 fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
 38   var self/esi: (addr gap-buffer) <- copy _self
 39   clear-stream out
 40   var left/eax: (addr grapheme-stack) <- get self, left
 41   emit-stack-from-bottom left, out
 42   var right/eax: (addr grapheme-stack) <- get self, right
 43   emit-stack-from-top right, out
 44 }
 45 
 46 # dump stack from bottom to top
 47 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
 48   var self/esi: (addr grapheme-stack) <- copy _self
 49   var data-ah/edi: (addr handle array grapheme) <- get self, data
 50   var _data/eax: (addr array grapheme) <- lookup *data-ah
 51   var data/edi: (addr array grapheme) <- copy _data
 52   var top-addr/ecx: (addr int) <- get self, top
 53   var i/eax: int <- copy 0
 54   {
 55     compare i, *top-addr
 56     break-if->=
 57     var g/edx: (addr grapheme) <- index data, i
 58     write-grapheme out, *g
 59     i <- increment
 60     loop
 61   }
 62 }
 63 
 64 # dump stack from top to bottom
 65 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
 66   var self/esi: (addr grapheme-stack) <- copy _self
 67   var data-ah/edi: (addr handle array grapheme) <- get self, data
 68   var _data/eax: (addr array grapheme) <- lookup *data-ah
 69   var data/edi: (addr array grapheme) <- copy _data
 70   var top-addr/ecx: (addr int) <- get self, top
 71   var i/eax: int <- copy *top-addr
 72   i <- decrement
 73   {
 74     compare i, 0
 75     break-if-<
 76     var g/edx: (addr grapheme) <- index data, i
 77     write-grapheme out, *g
 78     i <- decrement
 79     loop
 80   }
 81 }
 82 
 83 fn render-gap-buffer screen: (addr screen), _gap: (addr gap-buffer) {
 84   var gap/esi: (addr gap-buffer) <- copy _gap
 85   var left/eax: (addr grapheme-stack) <- get gap, left
 86   render-stack-from-bottom left, screen
 87   var right/eax: (addr grapheme-stack) <- get gap, right
 88   render-stack-from-top right, screen
 89 }
 90 
 91 fn gap-buffer-length _gap: (addr gap-buffer) -> result/eax: int {
 92   var gap/esi: (addr gap-buffer) <- copy _gap
 93   var left/eax: (addr grapheme-stack) <- get gap, left
 94   var tmp/eax: (addr int) <- get left, top
 95   var left-length/ecx: int <- copy *tmp
 96   var right/esi: (addr grapheme-stack) <- get gap, right
 97   tmp <- get right, top
 98   result <- copy *tmp
 99   result <- add left-length
100 }
101 
102 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
103   var self/esi: (addr gap-buffer) <- copy _self
104   var left/eax: (addr grapheme-stack) <- get self, left
105   push-grapheme-stack left, g
106 }
107 
108 fn gap-to-start self: (addr gap-buffer) {
109   {
110     var curr/eax: grapheme <- gap-left self
111     compare curr, -1
112     loop-if-!=
113   }
114 }
115 
116 fn gap-to-end self: (addr gap-buffer) {
117   {
118     var curr/eax: grapheme <- gap-right self
119     compare curr, -1
120     loop-if-!=
121   }
122 }
123 
124 fn gap-at-start? _self: (addr gap-buffer) -> result/eax: boolean {
125   var self/esi: (addr gap-buffer) <- copy _self
126   var left/eax: (addr grapheme-stack) <- get self, left
127   result <- grapheme-stack-empty? left
128 }
129 
130 fn gap-at-end? _self: (addr gap-buffer) -> result/eax: boolean {
131   var self/esi: (addr gap-buffer) <- copy _self
132   var right/eax: (addr grapheme-stack) <- get self, right
133   result <- grapheme-stack-empty? right
134 }
135 
136 fn gap-right _self: (addr gap-buffer) -> result/eax: grapheme {
137 $gap-right:body: {
138   var self/esi: (addr gap-buffer) <- copy _self
139   var g/edx: grapheme <- copy 0
140   {
141     var right/ecx: (addr grapheme-stack) <- get self, right
142     result <- pop-grapheme-stack right
143     compare result, -1
144     break-if-= $gap-right:body
145     g <- copy result
146   }
147   {
148     var left/ecx: (addr grapheme-stack) <- get self, left
149     push-grapheme-stack left, g
150   }
151 }
152 }
153 
154 fn gap-left _self: (addr gap-buffer) -> result/eax: grapheme {
155 $gap-left:body: {
156   var self/esi: (addr gap-buffer) <- copy _self
157   var g/edx: grapheme <- copy 0
158   {
159     var left/ecx: (addr grapheme-stack) <- get self, left
160     result <- pop-grapheme-stack left
161     compare result, -1
162     break-if-= $gap-left:body
163     g <- copy result
164   }
165   {
166     var right/ecx: (addr grapheme-stack) <- get self, right
167     push-grapheme-stack right, g
168   }
169 }
170 }
171 
172 fn gap-index _self: (addr gap-buffer) -> result/eax: int {
173   var self/eax: (addr gap-buffer) <- copy _self
174   var left/eax: (addr grapheme-stack) <- get self, left
175   var top-addr/eax: (addr int) <- get left, top
176   result <- copy *top-addr
177 }
178 
179 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> result/eax: grapheme {
180 $first-grapheme-in-gap-buffer:body: {
181   var self/esi: (addr gap-buffer) <- copy _self
182   # try to read from left
183   var left/eax: (addr grapheme-stack) <- get self, left
184   var top-addr/ecx: (addr int) <- get left, top
185   compare *top-addr, 0
186   {
187     break-if-<=
188     var data-ah/eax: (addr handle array grapheme) <- get left, data
189     var data/eax: (addr array grapheme) <- lookup *data-ah
190     var result-addr/eax: (addr grapheme) <- index data, 0
191     result <- copy *result-addr
192     break $first-grapheme-in-gap-buffer:body
193   }
194   # try to read from right
195   var right/eax: (addr grapheme-stack) <- get self, right
196   top-addr <- get right, top
197   compare *top-addr, 0
198   {
199     break-if-<=
200     var data-ah/eax: (addr handle array grapheme) <- get right, data
201     var data/eax: (addr array grapheme) <- lookup *data-ah
202     var top/ecx: int <- copy *top-addr
203     top <- decrement
204     var result-addr/eax: (addr grapheme) <- index data, top
205     result <- copy *result-addr
206     break $first-grapheme-in-gap-buffer:body
207   }
208   # give up
209   result <- copy -1
210 }
211 }
212 
213 fn delete-before-gap _self: (addr gap-buffer) {
214   var self/eax: (addr gap-buffer) <- copy _self
215   var left/eax: (addr grapheme-stack) <- get self, left
216   var dummy/eax: grapheme <- pop-grapheme-stack left
217 }
218 
219 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> result/eax: boolean {
220 $gap-buffer-equal?:body: {
221   var self/esi: (addr gap-buffer) <- copy _self
222   # complication: graphemes may be multiple bytes
223   # so don't rely on length
224   # instead turn the expected result into a stream and arrange to read from it in order
225   var stream-storage: (stream byte 0x10)  # max-word-size
226   var expected-stream/ecx: (addr stream byte) <- address stream-storage
227   write expected-stream, s
228   # compare left
229   var left/edx: (addr grapheme-stack) <- get self, left
230   result <- prefix-match? left, expected-stream
231   compare result, 0  # false
232   break-if-= $gap-buffer-equal?:body
233   # compare right
234   var right/edx: (addr grapheme-stack) <- get self, right
235   result <- suffix-match? right, expected-stream
236   compare result, 0  # false
237   break-if-= $gap-buffer-equal?:body
238   # ensure there's nothing left over
239   result <- stream-empty? expected-stream
240 }
241 }
242 
243 fn test-gap-buffer-equal-from-end? {
244   var _g: gap-buffer
245   var g/esi: (addr gap-buffer) <- address _g
246   initialize-gap-buffer g
247   #
248   var c/eax: grapheme <- copy 0x61  # 'a'
249   add-grapheme-at-gap g, c
250   add-grapheme-at-gap g, c
251   add-grapheme-at-gap g, c
252   # gap is at end (right is empty)
253   var _result/eax: boolean <- gap-buffer-equal? g, "aaa"
254   var result/eax: int <- copy _result
255   check-ints-equal result, 1, "F - test-gap-buffer-equal-from-end?"
256 }
257 
258 fn test-gap-buffer-equal-from-middle? {
259   var _g: gap-buffer
260   var g/esi: (addr gap-buffer) <- address _g
261   initialize-gap-buffer g
262   #
263   var c/eax: grapheme <- copy 0x61  # 'a'
264   add-grapheme-at-gap g, c
265   add-grapheme-at-gap g, c
266   add-grapheme-at-gap g, c
267   var dummy/eax: grapheme <- gap-left g
268   # gap is in the middle
269   var _result/eax: boolean <- gap-buffer-equal? g, "aaa"
270   var result/eax: int <- copy _result
271   check-ints-equal result, 1, "F - test-gap-buffer-equal-from-middle?"
272 }
273 
274 fn test-gap-buffer-equal-from-start? {
275   var _g: gap-buffer
276   var g/esi: (addr gap-buffer) <- address _g
277   initialize-gap-buffer g
278   #
279   var c/eax: grapheme <- copy 0x61  # 'a'
280   add-grapheme-at-gap g, c
281   add-grapheme-at-gap g, c
282   add-grapheme-at-gap g, c
283   var dummy/eax: grapheme <- gap-left g
284   dummy <- gap-left g
285   dummy <- gap-left g
286   # gap is at the start
287   var _result/eax: boolean <- gap-buffer-equal? g, "aaa"
288   var result/eax: int <- copy _result
289   check-ints-equal result, 1, "F - test-gap-buffer-equal-from-start?"
290 }