https://github.com/akkartik/mu/blob/main/shell/gap-buffer.mu
  1 # primitive for editing a single word
  2 
  3 type gap-buffer {
  4   left: grapheme-stack
  5   right: grapheme-stack
  6   # some fields for scanning incrementally through a gap-buffer
  7   left-read-index: int
  8   right-read-index: int
  9 }
 10 
 11 fn initialize-gap-buffer _self: (addr gap-buffer), max-word-size: int {
 12   var self/esi: (addr gap-buffer) <- copy _self
 13   var left/eax: (addr grapheme-stack) <- get self, left
 14   initialize-grapheme-stack left, max-word-size
 15   var right/eax: (addr grapheme-stack) <- get self, right
 16   initialize-grapheme-stack right, max-word-size
 17 }
 18 
 19 # just for tests
 20 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
 21   initialize-gap-buffer self, 0x10/max-word-size
 22   var stream-storage: (stream byte 0x10/max-word-size)
 23   var stream/ecx: (addr stream byte) <- address stream-storage
 24   write stream, s
 25   {
 26     var done?/eax: boolean <- stream-empty? stream
 27     compare done?, 0/false
 28     break-if-!=
 29     var g/eax: grapheme <- read-grapheme stream
 30     add-grapheme-at-gap self, g
 31     loop
 32   }
 33 }
 34 
 35 fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
 36   var self/esi: (addr gap-buffer) <- copy _self
 37   clear-stream out
 38   var left/eax: (addr grapheme-stack) <- get self, left
 39   emit-stack-from-bottom left, out
 40   var right/eax: (addr grapheme-stack) <- get self, right
 41   emit-stack-from-top right, out
 42 }
 43 
 44 # dump stack from bottom to top
 45 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
 46   var self/esi: (addr grapheme-stack) <- copy _self
 47   var data-ah/edi: (addr handle array grapheme) <- get self, data
 48   var _data/eax: (addr array grapheme) <- lookup *data-ah
 49   var data/edi: (addr array grapheme) <- copy _data
 50   var top-addr/ecx: (addr int) <- get self, top
 51   var i/eax: int <- copy 0
 52   {
 53     compare i, *top-addr
 54     break-if->=
 55     var g/edx: (addr grapheme) <- index data, i
 56     write-grapheme out, *g
 57     i <- increment
 58     loop
 59   }
 60 }
 61 
 62 # dump stack from top to bottom
 63 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
 64   var self/esi: (addr grapheme-stack) <- copy _self
 65   var data-ah/edi: (addr handle array grapheme) <- get self, data
 66   var _data/eax: (addr array grapheme) <- lookup *data-ah
 67   var data/edi: (addr array grapheme) <- copy _data
 68   var top-addr/ecx: (addr int) <- get self, top
 69   var i/eax: int <- copy *top-addr
 70   i <- decrement
 71   {
 72     compare i, 0
 73     break-if-<
 74     var g/edx: (addr grapheme) <- index data, i
 75     write-grapheme out, *g
 76     i <- decrement
 77     loop
 78   }
 79 }
 80 
 81 # We implicitly render everything editable in a single color, and assume the
 82 # cursor is a single other color.
 83 fn render-gap-buffer-wrapping-right-then-down screen: (addr screen), _gap: (addr gap-buffer), xmin: int, ymin: int, xmax: int, ymax: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
 84   var gap/esi: (addr gap-buffer) <- copy _gap
 85   var left/edx: (addr grapheme-stack) <- get gap, left
 86   var x2/eax: int <- copy 0
 87   var y2/ecx: int <- copy 0
 88   x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, left, xmin, ymin, xmax, ymax, xmin, ymin
 89   var right/edx: (addr grapheme-stack) <- get gap, right
 90   x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, right, xmin, ymin, xmax, ymax, x2, y2, render-cursor?
 91   # decide whether we still need to print a cursor
 92   var bg/ebx: int <- copy 0
 93   compare render-cursor?, 0/false
 94   {
 95     break-if-=
 96     # if the right side is empty, grapheme stack didn't print the cursor
 97     var empty?/eax: boolean <- grapheme-stack-empty? right
 98     compare empty?, 0/false
 99     break-if-=
100     bg <- copy 7/cursor
101   }
102   # print a grapheme either way so that cursor position doesn't affect printed width
103   var space/edx: grapheme <- copy 0x20
104   x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, 3/fg=cyan, bg
105   return x2, y2
106 }
107 
108 fn render-gap-buffer screen: (addr screen), gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean -> _/eax: int {
109   var _width/eax: int <- copy 0
110   var _height/ecx: int <- copy 0
111   _width, _height <- screen-size screen
112   var width/edx: int <- copy _width
113   var height/ebx: int <- copy _height
114   var x2/eax: int <- copy 0
115   var y2/ecx: int <- copy 0
116   x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?
117   return x2  # y2? yolo
118 }
119 
120 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
121   var gap/esi: (addr gap-buffer) <- copy _gap
122   var left/eax: (addr grapheme-stack) <- get gap, left
123   var tmp/eax: (addr int) <- get left, top
124   var left-length/ecx: int <- copy *tmp
125   var right/esi: (addr grapheme-stack) <- get gap, right
126   tmp <- get right, top
127   var result/eax: int <- copy *tmp
128   result <- add left-length
129   return result
130 }
131 
132 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
133   var self/esi: (addr gap-buffer) <- copy _self
134   var left/eax: (addr grapheme-stack) <- get self, left
135   push-grapheme-stack left, g
136 }
137 
138 fn gap-to-start self: (addr gap-buffer) {
139   {
140     var curr/eax: grapheme <- gap-left self
141     compare curr, -1
142     loop-if-!=
143   }
144 }
145 
146 fn gap-to-end self: (addr gap-buffer) {
147   {
148     var curr/eax: grapheme <- gap-right self
149     compare curr, -1
150     loop-if-!=
151   }
152 }
153 
154 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
155   var self/esi: (addr gap-buffer) <- copy _self
156   var left/eax: (addr grapheme-stack) <- get self, left
157   var result/eax: boolean <- grapheme-stack-empty? left
158   return result
159 }
160 
161 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
162   var self/esi: (addr gap-buffer) <- copy _self
163   var right/eax: (addr grapheme-stack) <- get self, right
164   var result/eax: boolean <- grapheme-stack-empty? right
165   return result
166 }
167 
168 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
169   var self/esi: (addr gap-buffer) <- copy _self
170   var g/eax: grapheme <- copy 0
171   var right/ecx: (addr grapheme-stack) <- get self, right
172   g <- pop-grapheme-stack right
173   compare g, -1
174   {
175     break-if-=
176     var left/ecx: (addr grapheme-stack) <- get self, left
177     push-grapheme-stack left, g
178   }
179   return g
180 }
181 
182 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
183   var self/esi: (addr gap-buffer) <- copy _self
184   var g/eax: grapheme <- copy 0
185   {
186     var left/ecx: (addr grapheme-stack) <- get self, left
187     g <- pop-grapheme-stack left
188   }
189   compare g, -1
190   {
191     break-if-=
192     var right/ecx: (addr grapheme-stack) <- get self, right
193     push-grapheme-stack right, g
194   }
195   return g
196 }
197 
198 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
199   var self/eax: (addr gap-buffer) <- copy _self
200   var left/eax: (addr grapheme-stack) <- get self, left
201   var top-addr/eax: (addr int) <- get left, top
202   var result/eax: int <- copy *top-addr
203   return result
204 }
205 
206 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
207   var self/esi: (addr gap-buffer) <- copy _self
208   # try to read from left
209   var left/eax: (addr grapheme-stack) <- get self, left
210   var top-addr/ecx: (addr int) <- get left, top
211   compare *top-addr, 0
212   {
213     break-if-<=
214     var data-ah/eax: (addr handle array grapheme) <- get left, data
215     var data/eax: (addr array grapheme) <- lookup *data-ah
216     var result-addr/eax: (addr grapheme) <- index data, 0
217     return *result-addr
218   }
219   # try to read from right
220   var right/eax: (addr grapheme-stack) <- get self, right
221   top-addr <- get right, top
222   compare *top-addr, 0
223   {
224     break-if-<=
225     var data-ah/eax: (addr handle array grapheme) <- get right, data
226     var data/eax: (addr array grapheme) <- lookup *data-ah
227     var top/ecx: int <- copy *top-addr
228     top <- decrement
229     var result-addr/eax: (addr grapheme) <- index data, top
230     return *result-addr
231   }
232   # give up
233   return -1
234 }
235 
236 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
237   var self/esi: (addr gap-buffer) <- copy _self
238   # try to read from left
239   var left/ecx: (addr grapheme-stack) <- get self, left
240   var top-addr/edx: (addr int) <- get left, top
241   compare *top-addr, 0
242   {
243     break-if-<=
244     var result/eax: grapheme <- pop-grapheme-stack left
245     push-grapheme-stack left, result
246     return result
247   }
248   # give up
249   return -1
250 }
251 
252 fn delete-before-gap _self: (addr gap-buffer) {
253   var self/eax: (addr gap-buffer) <- copy _self
254   var left/eax: (addr grapheme-stack) <- get self, left
255   var dummy/eax: grapheme <- pop-grapheme-stack left
256 }
257 
258 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
259   var self/eax: (addr gap-buffer) <- copy _self
260   var right/eax: (addr grapheme-stack) <- get self, right
261   var result/eax: grapheme <- pop-grapheme-stack right
262   return result
263 }
264 
265 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
266   var self/esi: (addr gap-buffer) <- copy _self
267   # complication: graphemes may be multiple bytes
268   # so don't rely on length
269   # instead turn the expected result into a stream and arrange to read from it in order
270   var stream-storage: (stream byte 0x10/max-word-size)
271   var expected-stream/ecx: (addr stream byte) <- address stream-storage
272   write expected-stream, s
273   # compare left
274   var left/edx: (addr grapheme-stack) <- get self, left
275   var result/eax: boolean <- prefix-match? left, expected-stream
276   compare result, 0/false
277   {
278     break-if-!=
279     return result
280   }
281   # compare right
282   var right/edx: (addr grapheme-stack) <- get self, right
283   result <- suffix-match? right, expected-stream
284   compare result, 0/false
285   {
286     break-if-!=
287     return result
288   }
289   # ensure there's nothing left over
290   result <- stream-empty? expected-stream
291   return result
292 }
293 
294 fn test-gap-buffer-equal-from-end {
295   var _g: gap-buffer
296   var g/esi: (addr gap-buffer) <- address _g
297   initialize-gap-buffer g, 0x10
298   #
299   var c/eax: grapheme <- copy 0x61/a
300   add-grapheme-at-gap g, c
301   add-grapheme-at-gap g, c
302   add-grapheme-at-gap g, c
303   # gap is at end (right is empty)
304   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
305   check result, "F - test-gap-buffer-equal-from-end"
306 }
307 
308 fn test-gap-buffer-equal-from-middle {
309   var _g: gap-buffer
310   var g/esi: (addr gap-buffer) <- address _g
311   initialize-gap-buffer g, 0x10
312   #
313   var c/eax: grapheme <- copy 0x61/a
314   add-grapheme-at-gap g, c
315   add-grapheme-at-gap g, c
316   add-grapheme-at-gap g, c
317   var dummy/eax: grapheme <- gap-left g
318   # gap is in the middle
319   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
320   check result, "F - test-gap-buffer-equal-from-middle"
321 }
322 
323 fn test-gap-buffer-equal-from-start {
324   var _g: gap-buffer
325   var g/esi: (addr gap-buffer) <- address _g
326   initialize-gap-buffer g, 0x10
327   #
328   var c/eax: grapheme <- copy 0x61/a
329   add-grapheme-at-gap g, c
330   add-grapheme-at-gap g, c
331   add-grapheme-at-gap g, c
332   var dummy/eax: grapheme <- gap-left g
333   dummy <- gap-left g
334   dummy <- gap-left g
335   # gap is at the start
336   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
337   check result, "F - test-gap-buffer-equal-from-start"
338 }
339 
340 fn test-gap-buffer-equal-fails {
341   # g = "aaa"
342   var _g: gap-buffer
343   var g/esi: (addr gap-buffer) <- address _g
344   initialize-gap-buffer g, 0x10
345   var c/eax: grapheme <- copy 0x61/a
346   add-grapheme-at-gap g, c
347   add-grapheme-at-gap g, c
348   add-grapheme-at-gap g, c
349   #
350   var result/eax: boolean <- gap-buffer-equal? g, "aa"
351   check-not result, "F - test-gap-buffer-equal-fails"
352 }
353 
354 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
355   var tmp/eax: int <- gap-buffer-length self
356   var len/ecx: int <- copy tmp
357   var leng/eax: int <- gap-buffer-length g
358   compare len, leng
359   {
360     break-if-=
361     return 0/false
362   }
363   var i/edx: int <- copy 0
364   {
365     compare i, len
366     break-if->=
367     {
368       var tmp/eax: grapheme <- gap-index self, i
369       var curr/ecx: grapheme <- copy tmp
370       var currg/eax: grapheme <- gap-index g, i
371       compare curr, currg
372       break-if-=
373       return 0/false
374     }
375     i <- increment
376     loop
377   }
378   return 1/true
379 }
380 
381 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
382   var self/esi: (addr gap-buffer) <- copy _self
383   var n/ebx: int <- copy _n
384   # if n < left->length, index into left
385   var left/edi: (addr grapheme-stack) <- get self, left
386   var left-len-a/edx: (addr int) <- get left, top
387   compare n, *left-len-a
388   {
389     break-if->=
390     var data-ah/eax: (addr handle array grapheme) <- get left, data
391     var data/eax: (addr array grapheme) <- lookup *data-ah
392     var result/eax: (addr grapheme) <- index data, n
393     return *result
394   }
395   # shrink n
396   n <- subtract *left-len-a
397   # if n < right->length, index into right
398   var right/edi: (addr grapheme-stack) <- get self, right
399   var right-len-a/edx: (addr int) <- get right, top
400   compare n, *right-len-a
401   {
402     break-if->=
403     var data-ah/eax: (addr handle array grapheme) <- get right, data
404     var data/eax: (addr array grapheme) <- lookup *data-ah
405     # idx = right->len - n - 1
406     var idx/ebx: int <- copy n
407     idx <- subtract *right-len-a
408     idx <- negate
409     idx <- subtract 1
410     var result/eax: (addr grapheme) <- index data, idx
411     return *result
412   }
413   # error
414   abort "gap-index: out of bounds"
415   return 0
416 }
417 
418 fn test-gap-buffers-equal? {
419   var _a: gap-buffer
420   var a/esi: (addr gap-buffer) <- address _a
421   initialize-gap-buffer-with a, "abc"
422   var _b: gap-buffer
423   var b/edi: (addr gap-buffer) <- address _b
424   initialize-gap-buffer-with b, "abc"
425   var _c: gap-buffer
426   var c/ebx: (addr gap-buffer) <- address _c
427   initialize-gap-buffer-with c, "ab"
428   var _d: gap-buffer
429   var d/edx: (addr gap-buffer) <- address _d
430   initialize-gap-buffer-with d, "abd"
431   #
432   var result/eax: boolean <- gap-buffers-equal? a, a
433   check result, "F - test-gap-buffers-equal? - reflexive"
434   result <- gap-buffers-equal? a, b
435   check result, "F - test-gap-buffers-equal? - equal"
436   # length not equal
437   result <- gap-buffers-equal? a, c
438   check-not result, "F - test-gap-buffers-equal? - not equal"
439   # contents not equal
440   result <- gap-buffers-equal? a, d
441   check-not result, "F - test-gap-buffers-equal? - not equal 2"
442   result <- gap-buffers-equal? d, a
443   check-not result, "F - test-gap-buffers-equal? - not equal 3"
444 }
445 
446 fn test-gap-buffer-index {
447   var gap-storage: gap-buffer
448   var gap/esi: (addr gap-buffer) <- address gap-storage
449   initialize-gap-buffer-with gap, "abc"
450   # gap is at end, all contents are in left
451   var g/eax: grapheme <- gap-index gap, 0
452   var x/ecx: int <- copy g
453   check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
454   var g/eax: grapheme <- gap-index gap, 1
455   var x/ecx: int <- copy g
456   check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
457   var g/eax: grapheme <- gap-index gap, 2
458   var x/ecx: int <- copy g
459   check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
460   # now check when everything is to the right
461   gap-to-start gap
462   rewind-gap-buffer gap
463   var g/eax: grapheme <- gap-index gap, 0
464   var x/ecx: int <- copy g
465   check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
466   var g/eax: grapheme <- gap-index gap, 1
467   var x/ecx: int <- copy g
468   check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
469   var g/eax: grapheme <- gap-index gap, 2
470   var x/ecx: int <- copy g
471   check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
472 }
473 
474 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
475   # obtain src-a, dest-a
476   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
477   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
478   var src-a/esi: (addr gap-buffer) <- copy _src-a
479   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
480   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
481   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
482   # copy left grapheme-stack
483   var src/ecx: (addr grapheme-stack) <- get src-a, left
484   var dest/edx: (addr grapheme-stack) <- get dest-a, left
485   copy-grapheme-stack src, dest
486   # copy right grapheme-stack
487   src <- get src-a, right
488   dest <- get dest-a, right
489   copy-grapheme-stack src, dest
490 }
491 
492 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
493   var self/esi: (addr gap-buffer) <- copy _self
494   var curr/ecx: (addr grapheme-stack) <- get self, left
495   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
496   {
497     compare result, 0/false
498     break-if-=
499     curr <- get self, right
500     result <- grapheme-stack-is-decimal-integer? curr
501   }
502   return result
503 }
504 
505 fn test-render-gap-buffer-without-cursor {
506   # setup
507   var gap-storage: gap-buffer
508   var gap/esi: (addr gap-buffer) <- address gap-storage
509   initialize-gap-buffer-with gap, "abc"
510   # setup: screen
511   var screen-on-stack: screen
512   var screen/edi: (addr screen) <- address screen-on-stack
513   initialize-screen screen, 5, 4
514   #
515   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor
516   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
517   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
518                                                                 # abc
519   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
520 }
521 
522 fn test-render-gap-buffer-with-cursor-at-end {
523   # setup
524   var gap-storage: gap-buffer
525   var gap/esi: (addr gap-buffer) <- address gap-storage
526   initialize-gap-buffer-with gap, "abc"
527   gap-to-end gap
528   # setup: screen
529   var screen-on-stack: screen
530   var screen/edi: (addr screen) <- address screen-on-stack
531   initialize-screen screen, 5, 4
532   #
533   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
534   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
535   # we've drawn one extra grapheme for the cursor
536   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
537                                                                 # abc
538   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
539 }
540 
541 fn test-render-gap-buffer-with-cursor-in-middle {
542   # setup
543   var gap-storage: gap-buffer
544   var gap/esi: (addr gap-buffer) <- address gap-storage
545   initialize-gap-buffer-with gap, "abc"
546   gap-to-end gap
547   var dummy/eax: grapheme <- gap-left gap
548   # setup: screen
549   var screen-on-stack: screen
550   var screen/edi: (addr screen) <- address screen-on-stack
551   initialize-screen screen, 5, 4
552   #
553   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
554   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
555   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
556                                                                 # abc
557   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
558 }
559 
560 fn test-render-gap-buffer-with-cursor-at-start {
561   var gap-storage: gap-buffer
562   var gap/esi: (addr gap-buffer) <- address gap-storage
563   initialize-gap-buffer-with gap, "abc"
564   gap-to-start gap
565   # setup: screen
566   var screen-on-stack: screen
567   var screen/edi: (addr screen) <- address screen-on-stack
568   initialize-screen screen, 5, 4
569   #
570   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
571   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
572   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
573                                                                 # abc
574   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
575 }
576 
577 ## some primitives for scanning through a gap buffer
578 # don't modify the gap buffer while scanning
579 # this includes moving the cursor around
580 
581 # restart scan without affecting gap-buffer contents
582 fn rewind-gap-buffer _self: (addr gap-buffer) {
583   var self/esi: (addr gap-buffer) <- copy _self
584   var dest/eax: (addr int) <- get self, left-read-index
585   copy-to *dest, 0
586   dest <- get self, right-read-index
587   copy-to *dest, 0
588 }
589 
590 fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
591   var self/esi: (addr gap-buffer) <- copy _self
592   # more in left?
593   var left/eax: (addr grapheme-stack) <- get self, left
594   var left-size/eax: int <- grapheme-stack-length left
595   var left-read-index/ecx: (addr int) <- get self, left-read-index
596   compare *left-read-index, left-size
597   {
598     break-if->=
599     return 0/false
600   }
601   # more in right?
602   var right/eax: (addr grapheme-stack) <- get self, right
603   var right-size/eax: int <- grapheme-stack-length right
604   var right-read-index/ecx: (addr int) <- get self, right-read-index
605   compare *right-read-index, right-size
606   {
607     break-if->=
608     return 0/false
609   }
610   #
611   return 1/true
612 }
613 
614 fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
615   var self/esi: (addr gap-buffer) <- copy _self
616   # more in left?
617   var left/ecx: (addr grapheme-stack) <- get self, left
618   var left-size/eax: int <- grapheme-stack-length left
619   var left-read-index-a/edx: (addr int) <- get self, left-read-index
620   compare *left-read-index-a, left-size
621   {
622     break-if->=
623     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
624     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
625     var left-read-index/ecx: int <- copy *left-read-index-a
626     var result/eax: (addr grapheme) <- index left-data, left-read-index
627     return *result
628   }
629   # more in right?
630   var right/ecx: (addr grapheme-stack) <- get self, right
631   var _right-size/eax: int <- grapheme-stack-length right
632   var right-size/ebx: int <- copy _right-size
633   var right-read-index-a/edx: (addr int) <- get self, right-read-index
634   compare *right-read-index-a, right-size
635   {
636     break-if->=
637     # read the right from reverse
638     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
639     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
640     var right-read-index/ebx: int <- copy right-size
641     right-read-index <- subtract *right-read-index-a
642     right-read-index <- subtract 1
643     var result/eax: (addr grapheme) <- index right-data, right-read-index
644     return *result
645   }
646   # if we get here there's nothing left
647   return 0/nul
648 }
649 
650 fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
651   var self/esi: (addr gap-buffer) <- copy _self
652   # more in left?
653   var left/ecx: (addr grapheme-stack) <- get self, left
654   var left-size/eax: int <- grapheme-stack-length left
655   var left-read-index-a/edx: (addr int) <- get self, left-read-index
656   compare *left-read-index-a, left-size
657   {
658     break-if->=
659     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
660     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
661     var left-read-index/ecx: int <- copy *left-read-index-a
662     var result/eax: (addr grapheme) <- index left-data, left-read-index
663     increment *left-read-index-a
664     return *result
665   }
666   # more in right?
667   var right/ecx: (addr grapheme-stack) <- get self, right
668   var _right-size/eax: int <- grapheme-stack-length right
669   var right-size/ebx: int <- copy _right-size
670   var right-read-index-a/edx: (addr int) <- get self, right-read-index
671   compare *right-read-index-a, right-size
672   {
673     break-if->=
674     # read the right from reverse
675     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
676     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
677     var right-read-index/ebx: int <- copy right-size
678     right-read-index <- subtract *right-read-index-a
679     right-read-index <- subtract 1
680     var result/eax: (addr grapheme) <- index right-data, right-read-index
681     increment *right-read-index-a
682     return *result
683   }
684   # if we get here there's nothing left
685   return 0/nul
686 }
687 
688 fn test-read-from-gap-buffer {
689   var gap-storage: gap-buffer
690   var gap/esi: (addr gap-buffer) <- address gap-storage
691   initialize-gap-buffer-with gap, "abc"
692   # gap is at end, all contents are in left
693   var done?/eax: boolean <- gap-buffer-scan-done? gap
694   check-not done?, "F - test-read-from-gap-buffer/left-1/done"
695   var g/eax: grapheme <- read-from-gap-buffer gap
696   var x/ecx: int <- copy g
697   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
698   var done?/eax: boolean <- gap-buffer-scan-done? gap
699   check-not done?, "F - test-read-from-gap-buffer/left-2/done"
700   var g/eax: grapheme <- read-from-gap-buffer gap
701   var x/ecx: int <- copy g
702   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
703   var done?/eax: boolean <- gap-buffer-scan-done? gap
704   check-not done?, "F - test-read-from-gap-buffer/left-3/done"
705   var g/eax: grapheme <- read-from-gap-buffer gap
706   var x/ecx: int <- copy g
707   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
708   var done?/eax: boolean <- gap-buffer-scan-done? gap
709   check done?, "F - test-read-from-gap-buffer/left-4/done"
710   var g/eax: grapheme <- read-from-gap-buffer gap
711   var x/ecx: int <- copy g
712   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
713   # now check when everything is to the right
714   gap-to-start gap
715   rewind-gap-buffer gap
716   var done?/eax: boolean <- gap-buffer-scan-done? gap
717   check-not done?, "F - test-read-from-gap-buffer/right-1/done"
718   var g/eax: grapheme <- read-from-gap-buffer gap
719   var x/ecx: int <- copy g
720   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
721   var done?/eax: boolean <- gap-buffer-scan-done? gap
722   check-not done?, "F - test-read-from-gap-buffer/right-2/done"
723   var g/eax: grapheme <- read-from-gap-buffer gap
724   var x/ecx: int <- copy g
725   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
726   var done?/eax: boolean <- gap-buffer-scan-done? gap
727   check-not done?, "F - test-read-from-gap-buffer/right-3/done"
728   var g/eax: grapheme <- read-from-gap-buffer gap
729   var x/ecx: int <- copy g
730   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
731   var done?/eax: boolean <- gap-buffer-scan-done? gap
732   check done?, "F - test-read-from-gap-buffer/right-4/done"
733   var g/eax: grapheme <- read-from-gap-buffer gap
734   var x/ecx: int <- copy g
735   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
736 }
737 
738 fn skip-whitespace-from-gap-buffer self: (addr gap-buffer) {
739   var done?/eax: boolean <- gap-buffer-scan-done? self
740   compare done?, 0/false
741   break-if-!=
742   var g/eax: grapheme <- peek-from-gap-buffer self
743   {
744     compare g, 0x20/space
745     break-if-=
746     compare g, 0xa/newline
747     break-if-=
748     return
749   }
750   g <- read-from-gap-buffer self
751   loop
752 }
753 
754 fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
755   var g/edx: grapheme <- copy key
756   {
757     compare g, 8/backspace
758     break-if-!=
759     delete-before-gap self
760     return
761   }
762   # arrow keys
763   {
764     compare g, 4/ctrl-d
765     break-if-!=
766     # ctrl-d: cursor down
767     return
768   }
769   {
770     compare g, 0x15/ctrl-u
771     break-if-!=
772     # ctrl-u: cursor up
773     return
774   }
775   # default: insert character
776   add-grapheme-at-gap self, g
777 }
778 
779 fn cursor-on-final-line? self: (addr gap-buffer) -> _/eax: boolean {
780   return 1/true
781 }