# Mu - shell/gap-buffer.mu

https://github.com/akkartik/mu/blob/main/shell/gap-buffer.mu

       1 # primitive for editing text
       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), capacity: 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, capacity
      15   var right/eax: (addr grapheme-stack) <- get self, right
      16   initialize-grapheme-stack right, capacity
      17 }
      18
      19 fn clear-gap-buffer _self: (addr gap-buffer) {
      20   var self/esi: (addr gap-buffer) <- copy _self
      21   var left/eax: (addr grapheme-stack) <- get self, left
      22   clear-grapheme-stack left
      23   var right/eax: (addr grapheme-stack) <- get self, right
      24   clear-grapheme-stack right
      25 }
      26
      27 fn gap-buffer-empty? _self: (addr gap-buffer) -> _/eax: boolean {
      28   var self/esi: (addr gap-buffer) <- copy _self
      29   # if !empty?(left) return false
      30   {
      31     var left/eax: (addr grapheme-stack) <- get self, left
      32     var result/eax: boolean <- grapheme-stack-empty? left
      33     compare result, 0/false
      34     break-if-!=
      35     return 0/false
      36   }
      37   # return empty?(right)
      38   var left/eax: (addr grapheme-stack) <- get self, left
      39   var result/eax: boolean <- grapheme-stack-empty? left
      40   return result
      41 }
      42
      43 fn gap-buffer-capacity _gap: (addr gap-buffer) -> _/ecx: int {
      44   var gap/esi: (addr gap-buffer) <- copy _gap
      45   var left/eax: (addr grapheme-stack) <- get gap, left
      46   var left-data-ah/eax: (addr handle array grapheme) <- get left, data
      47   var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
      48   var result/eax: int <- length left-data
      49   return result
      50 }
      51
      52 # just for tests
      53 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
      54   initialize-gap-buffer self, 0x40/capacity
      55   var stream-storage: (stream byte 0x40/capacity)
      56   var stream/ecx: (addr stream byte) <- address stream-storage
      57   write stream, s
      58   {
      59     var done?/eax: boolean <- stream-empty? stream
      60     compare done?, 0/false
      61     break-if-!=
      62     var g/eax: grapheme <- read-grapheme stream
      63     add-grapheme-at-gap self, g
      64     loop
      65   }
      66 }
      67
      68 fn load-gap-buffer-from-stream self: (addr gap-buffer), in: (addr stream byte) {
      69   rewind-stream in
      70   {
      71     var done?/eax: boolean <- stream-empty? in
      72     compare done?, 0/false
      73     break-if-!=
      74     var key/eax: byte <- read-byte in
      75     compare key, 0/null
      76     break-if-=
      77     var g/eax: grapheme <- copy key
      78     edit-gap-buffer self, g
      79     loop
      80   }
      81 }
      82
      83 fn emit-gap-buffer self: (addr gap-buffer), out: (addr stream byte) {
      84   clear-stream out
      85   append-gap-buffer self, out
      86 }
      87
      88 fn append-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
      89   var self/esi: (addr gap-buffer) <- copy _self
      90   var left/eax: (addr grapheme-stack) <- get self, left
      91   emit-stack-from-bottom left, out
      92   var right/eax: (addr grapheme-stack) <- get self, right
      93   emit-stack-from-top right, out
      94 }
      95
      96 # dump stack from bottom to top
      97 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
      98   var self/esi: (addr grapheme-stack) <- copy _self
      99   var data-ah/edi: (addr handle array grapheme) <- get self, data
     100   var _data/eax: (addr array grapheme) <- lookup *data-ah
     101   var data/edi: (addr array grapheme) <- copy _data
     102   var top-addr/ecx: (addr int) <- get self, top
     103   var i/eax: int <- copy 0
     104   {
     105     compare i, *top-addr
     106     break-if->=
     107     var g/edx: (addr grapheme) <- index data, i
     108     write-grapheme out, *g
     109     i <- increment
     110     loop
     111   }
     112 }
     113
     114 # dump stack from top to bottom
     115 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
     116   var self/esi: (addr grapheme-stack) <- copy _self
     117   var data-ah/edi: (addr handle array grapheme) <- get self, data
     118   var _data/eax: (addr array grapheme) <- lookup *data-ah
     119   var data/edi: (addr array grapheme) <- copy _data
     120   var top-addr/ecx: (addr int) <- get self, top
     121   var i/eax: int <- copy *top-addr
     122   i <- decrement
     123   {
     124     compare i, 0
     125     break-if-<
     126     var g/edx: (addr grapheme) <- index data, i
     127     write-grapheme out, *g
     128     i <- decrement
     129     loop
     130   }
     131 }
     132
     133 fn word-at-gap _self: (addr gap-buffer), out: (addr stream byte) {
     134   var self/esi: (addr gap-buffer) <- copy _self
     135   clear-stream out
     136   {
     137     var g/eax: grapheme <- grapheme-at-gap self
     138     var at-word?/eax: boolean <- is-ascii-word-grapheme? g
     139     compare at-word?, 0/false
     140     break-if-!=
     141     return
     142   }
     143   var left/ecx: (addr grapheme-stack) <- get self, left
     144   var left-index/eax: int <- top-most-word left
     145   emit-stack-from-index left, left-index, out
     146   var right/ecx: (addr grapheme-stack) <- get self, right
     147   var right-index/eax: int <- top-most-word right
     148   emit-stack-to-index right, right-index, out
     149 }
     150
     151 fn test-word-at-gap-single-word-with-gap-at-end {
     152   var _g: gap-buffer
     153   var g/esi: (addr gap-buffer) <- address _g
     154   initialize-gap-buffer-with g, "abc"
     155   # gap is at end (right is empty)
     156   var out-storage: (stream byte 0x10)
     157   var out/eax: (addr stream byte) <- address out-storage
     158   word-at-gap g, out
     159   check-stream-equal out, "abc", "F - test-word-at-gap-single-word-with-gap-at-end"
     160 }
     161
     162 fn test-word-at-gap-single-word-with-gap-at-start {
     163   var _g: gap-buffer
     164   var g/esi: (addr gap-buffer) <- address _g
     165   initialize-gap-buffer-with g, "abc"
     166   gap-to-start g
     167   #
     168   var out-storage: (stream byte 0x10)
     169   var out/eax: (addr stream byte) <- address out-storage
     170   word-at-gap g, out
     171   check-stream-equal out, "abc", "F - test-word-at-gap-single-word-with-gap-at-start"
     172 }
     173
     174 fn test-word-at-gap-multiple-words-with-gap-at-non-word-grapheme-at-end {
     175   var _g: gap-buffer
     176   var g/esi: (addr gap-buffer) <- address _g
     177   initialize-gap-buffer-with g, "abc "
     178   # gap is at end (right is empty)
     179   var out-storage: (stream byte 0x10)
     180   var out/eax: (addr stream byte) <- address out-storage
     181   word-at-gap g, out
     182   check-stream-equal out, "", "F - test-word-at-gap-multiple-words-with-gap-at-non-word-grapheme-at-end"
     183 }
     184
     185 fn test-word-at-gap-multiple-words-with-gap-at-non-word-grapheme-at-start {
     186   var _g: gap-buffer
     187   var g/esi: (addr gap-buffer) <- address _g
     188   initialize-gap-buffer-with g, " abc"
     189   gap-to-start g
     190   #
     191   var out-storage: (stream byte 0x10)
     192   var out/eax: (addr stream byte) <- address out-storage
     193   word-at-gap g, out
     194   check-stream-equal out, "", "F - test-word-at-gap-multiple-words-with-gap-at-non-word-grapheme-at-start"
     195 }
     196
     197 fn test-word-at-gap-multiple-words-with-gap-at-end {
     198   var _g: gap-buffer
     199   var g/esi: (addr gap-buffer) <- address _g
     200   initialize-gap-buffer-with g, "a bc d"
     201   # gap is at end (right is empty)
     202   var out-storage: (stream byte 0x10)
     203   var out/eax: (addr stream byte) <- address out-storage
     204   word-at-gap g, out
     205   check-stream-equal out, "d", "F - test-word-at-gap-multiple-words-with-gap-at-end"
     206 }
     207
     208 fn test-word-at-gap-multiple-words-with-gap-at-initial-word {
     209   var _g: gap-buffer
     210   var g/esi: (addr gap-buffer) <- address _g
     211   initialize-gap-buffer-with g, "a bc d"
     212   gap-to-start g
     213   #
     214   var out-storage: (stream byte 0x10)
     215   var out/eax: (addr stream byte) <- address out-storage
     216   word-at-gap g, out
     217   check-stream-equal out, "a", "F - test-word-at-gap-multiple-words-with-gap-at-initial-word"
     218 }
     219
     220 fn test-word-at-gap-multiple-words-with-gap-at-final-word {
     221   var _g: gap-buffer
     222   var g/esi: (addr gap-buffer) <- address _g
     223   initialize-gap-buffer-with g, "a bc d"
     224   var dummy/eax: grapheme <- gap-left g
     225   # gap is at final word
     226   var out-storage: (stream byte 0x10)
     227   var out/eax: (addr stream byte) <- address out-storage
     228   word-at-gap g, out
     229   check-stream-equal out, "d", "F - test-word-at-gap-multiple-words-with-gap-at-final-word"
     230 }
     231
     232 fn test-word-at-gap-multiple-words-with-gap-at-final-non-word {
     233   var _g: gap-buffer
     234   var g/esi: (addr gap-buffer) <- address _g
     235   initialize-gap-buffer-with g, "abc "
     236   var dummy/eax: grapheme <- gap-left g
     237   # gap is at final word
     238   var out-storage: (stream byte 0x10)
     239   var out/eax: (addr stream byte) <- address out-storage
     240   word-at-gap g, out
     241   check-stream-equal out, "", "F - test-word-at-gap-multiple-words-with-gap-at-final-non-word"
     242 }
     243
     244 fn grapheme-at-gap _self: (addr gap-buffer) -> _/eax: grapheme {
     245   # send top of right most of the time
     246   var self/esi: (addr gap-buffer) <- copy _self
     247   var right/edi: (addr grapheme-stack) <- get self, right
     248   var data-ah/eax: (addr handle array grapheme) <- get right, data
     249   var data/eax: (addr array grapheme) <- lookup *data-ah
     250   var top-addr/ecx: (addr int) <- get right, top
     251   {
     252     compare *top-addr, 0
     253     break-if-<=
     254     var top/ecx: int <- copy *top-addr
     255     top <- decrement
     256     var result/eax: (addr grapheme) <- index data, top
     257     return *result
     258   }
     259   # send top of left only if right is empty
     260   var left/edi: (addr grapheme-stack) <- get self, left
     261   var data-ah/eax: (addr handle array grapheme) <- get left, data
     262   var data/eax: (addr array grapheme) <- lookup *data-ah
     263   var top-addr/ecx: (addr int) <- get left, top
     264   {
     265     compare *top-addr, 0
     266     break-if-<=
     267     var top/ecx: int <- copy *top-addr
     268     top <- decrement
     269     var result/eax: (addr grapheme) <- index data, top
     270     return *result
     271   }
     272   # send null if everything is empty
     273   return 0
     274 }
     275
     276 fn top-most-word _self: (addr grapheme-stack) -> _/eax: int {
     277   var self/esi: (addr grapheme-stack) <- copy _self
     278   var data-ah/edi: (addr handle array grapheme) <- get self, data
     279   var _data/eax: (addr array grapheme) <- lookup *data-ah
     280   var data/edi: (addr array grapheme) <- copy _data
     281   var top-addr/ecx: (addr int) <- get self, top
     282   var i/ebx: int <- copy *top-addr
     283   i <- decrement
     284   {
     285     compare i, 0
     286     break-if-<
     287     var g/edx: (addr grapheme) <- index data, i
     288     var is-word?/eax: boolean <- is-ascii-word-grapheme? *g
     289     compare is-word?, 0/false
     290     break-if-=
     291     i <- decrement
     292     loop
     293   }
     294   i <- increment
     295   return i
     296 }
     297
     298 fn emit-stack-from-index _self: (addr grapheme-stack), start: int, out: (addr stream byte) {
     299   var self/esi: (addr grapheme-stack) <- copy _self
     300   var data-ah/edi: (addr handle array grapheme) <- get self, data
     301   var _data/eax: (addr array grapheme) <- lookup *data-ah
     302   var data/edi: (addr array grapheme) <- copy _data
     303   var top-addr/ecx: (addr int) <- get self, top
     304   var i/eax: int <- copy start
     305   {
     306     compare i, *top-addr
     307     break-if->=
     308     var g/edx: (addr grapheme) <- index data, i
     309     write-grapheme out, *g
     310     i <- increment
     311     loop
     312   }
     313 }
     314
     315 fn emit-stack-to-index _self: (addr grapheme-stack), end: int, out: (addr stream byte) {
     316   var self/esi: (addr grapheme-stack) <- copy _self
     317   var data-ah/edi: (addr handle array grapheme) <- get self, data
     318   var _data/eax: (addr array grapheme) <- lookup *data-ah
     319   var data/edi: (addr array grapheme) <- copy _data
     320   var top-addr/ecx: (addr int) <- get self, top
     321   var i/eax: int <- copy *top-addr
     322   i <- decrement
     323   {
     324     compare i, 0
     325     break-if-<
     326     compare i, end
     327     break-if-<
     328     var g/edx: (addr grapheme) <- index data, i
     329     write-grapheme out, *g
     330     i <- decrement
     331     loop
     332   }
     333 }
     334
     335 fn is-ascii-word-grapheme? g: grapheme -> _/eax: boolean {
     336   compare g, 0x21/!
     337   {
     338     break-if-!=
     339     return 1/true
     340   }
     341   compare g, 0x30/0
     342   {
     343     break-if->=
     344     return 0/false
     345   }
     346   compare g, 0x39/9
     347   {
     348     break-if->
     349     return 1/true
     350   }
     351   compare g, 0x3f/?
     352   {
     353     break-if-!=
     354     return 1/true
     355   }
     356   compare g, 0x41/A
     357   {
     358     break-if->=
     359     return 0/false
     360   }
     361   compare g, 0x5a/Z
     362   {
     363     break-if->
     364     return 1/true
     365   }
     366   compare g, 0x5f/_
     367   {
     368     break-if-!=
     369     return 1/true
     370   }
     371   compare g, 0x61/a
     372   {
     373     break-if->=
     374     return 0/false
     375   }
     376   compare g, 0x7a/z
     377   {
     378     break-if->
     379     return 1/true
     380   }
     381   return 0/false
     382 }
     383
     384 # We implicitly render everything editable in a single color, and assume the
     385 # cursor is a single other color.
     386 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, color: int, background-color: int -> _/eax: int, _/ecx: int {
     387   var gap/esi: (addr gap-buffer) <- copy _gap
     388   var left/edx: (addr grapheme-stack) <- get gap, left
     389   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
     390   var matching-open-paren-depth/edi: int <- copy 0
     391   highlight-matching-open-paren?, matching-open-paren-depth <- highlight-matching-open-paren? gap, render-cursor?
     392   var x2/eax: int <- copy 0
     393   var y2/ecx: int <- copy 0
     394   x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, left, xmin, ymin, xmax, ymax, xmin, ymin, highlight-matching-open-paren?, matching-open-paren-depth, color, background-color
     395   var right/edx: (addr grapheme-stack) <- get gap, right
     396   x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, right, xmin, ymin, xmax, ymax, x2, y2, render-cursor?, color, background-color
     397   # decide whether we still need to print a cursor
     398   var bg/ebx: int <- copy background-color
     399   compare render-cursor?, 0/false
     400   {
     401     break-if-=
     402     # if the right side is empty, grapheme stack didn't print the cursor
     403     var empty?/eax: boolean <- grapheme-stack-empty? right
     404     compare empty?, 0/false
     405     break-if-=
     406     bg <- copy 7/cursor
     407   }
     408   # print a grapheme either way so that cursor position doesn't affect printed width
     409   var space/edx: grapheme <- copy 0x20
     410   x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, color, bg
     411   return x2, y2
     412 }
     413
     414 fn render-gap-buffer screen: (addr screen), gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean, color: int, background-color: int -> _/eax: int {
     415   var _width/eax: int <- copy 0
     416   var _height/ecx: int <- copy 0
     417   _width, _height <- screen-size screen
     418   var width/edx: int <- copy _width
     419   var height/ebx: int <- copy _height
     420   var x2/eax: int <- copy 0
     421   var y2/ecx: int <- copy 0
     422   x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?, color, background-color
     423   return x2  # y2? yolo
     424 }
     425
     426 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
     427   var gap/esi: (addr gap-buffer) <- copy _gap
     428   var left/eax: (addr grapheme-stack) <- get gap, left
     429   var tmp/eax: (addr int) <- get left, top
     430   var left-length/ecx: int <- copy *tmp
     431   var right/esi: (addr grapheme-stack) <- get gap, right
     432   tmp <- get right, top
     433   var result/eax: int <- copy *tmp
     434   result <- add left-length
     435   return result
     436 }
     437
     438 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
     439   var self/esi: (addr gap-buffer) <- copy _self
     440   var left/eax: (addr grapheme-stack) <- get self, left
     441   push-grapheme-stack left, g
     442 }
     443
     444 fn add-code-point-at-gap self: (addr gap-buffer), c: code-point {
     445   var g/eax: grapheme <- copy c
     446   add-grapheme-at-gap self, g
     447 }
     448
     449 fn gap-to-start self: (addr gap-buffer) {
     450   {
     451     var curr/eax: grapheme <- gap-left self
     452     compare curr, -1
     453     loop-if-!=
     454   }
     455 }
     456
     457 fn gap-to-end self: (addr gap-buffer) {
     458   {
     459     var curr/eax: grapheme <- gap-right self
     460     compare curr, -1
     461     loop-if-!=
     462   }
     463 }
     464
     465 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
     466   var self/esi: (addr gap-buffer) <- copy _self
     467   var left/eax: (addr grapheme-stack) <- get self, left
     468   var result/eax: boolean <- grapheme-stack-empty? left
     469   return result
     470 }
     471
     472 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
     473   var self/esi: (addr gap-buffer) <- copy _self
     474   var right/eax: (addr grapheme-stack) <- get self, right
     475   var result/eax: boolean <- grapheme-stack-empty? right
     476   return result
     477 }
     478
     479 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
     480   var self/esi: (addr gap-buffer) <- copy _self
     481   var g/eax: grapheme <- copy 0
     482   var right/ecx: (addr grapheme-stack) <- get self, right
     483   g <- pop-grapheme-stack right
     484   compare g, -1
     485   {
     486     break-if-=
     487     var left/ecx: (addr grapheme-stack) <- get self, left
     488     push-grapheme-stack left, g
     489   }
     490   return g
     491 }
     492
     493 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
     494   var self/esi: (addr gap-buffer) <- copy _self
     495   var g/eax: grapheme <- copy 0
     496   {
     497     var left/ecx: (addr grapheme-stack) <- get self, left
     498     g <- pop-grapheme-stack left
     499   }
     500   compare g, -1
     501   {
     502     break-if-=
     503     var right/ecx: (addr grapheme-stack) <- get self, right
     504     push-grapheme-stack right, g
     505   }
     506   return g
     507 }
     508
     509 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
     510   var self/eax: (addr gap-buffer) <- copy _self
     511   var left/eax: (addr grapheme-stack) <- get self, left
     512   var top-addr/eax: (addr int) <- get left, top
     513   var result/eax: int <- copy *top-addr
     514   return result
     515 }
     516
     517 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     518   var self/esi: (addr gap-buffer) <- copy _self
     519   # try to read from left
     520   var left/eax: (addr grapheme-stack) <- get self, left
     521   var top-addr/ecx: (addr int) <- get left, top
     522   compare *top-addr, 0
     523   {
     524     break-if-<=
     525     var data-ah/eax: (addr handle array grapheme) <- get left, data
     526     var data/eax: (addr array grapheme) <- lookup *data-ah
     527     var result-addr/eax: (addr grapheme) <- index data, 0
     528     return *result-addr
     529   }
     530   # try to read from right
     531   var right/eax: (addr grapheme-stack) <- get self, right
     532   top-addr <- get right, top
     533   compare *top-addr, 0
     534   {
     535     break-if-<=
     536     var data-ah/eax: (addr handle array grapheme) <- get right, data
     537     var data/eax: (addr array grapheme) <- lookup *data-ah
     538     var top/ecx: int <- copy *top-addr
     539     top <- decrement
     540     var result-addr/eax: (addr grapheme) <- index data, top
     541     return *result-addr
     542   }
     543   # give up
     544   return -1
     545 }
     546
     547 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     548   var self/esi: (addr gap-buffer) <- copy _self
     549   # try to read from left
     550   var left/ecx: (addr grapheme-stack) <- get self, left
     551   var top-addr/edx: (addr int) <- get left, top
     552   compare *top-addr, 0
     553   {
     554     break-if-<=
     555     var result/eax: grapheme <- pop-grapheme-stack left
     556     push-grapheme-stack left, result
     557     return result
     558   }
     559   # give up
     560   return -1
     561 }
     562
     563 fn delete-before-gap _self: (addr gap-buffer) {
     564   var self/eax: (addr gap-buffer) <- copy _self
     565   var left/eax: (addr grapheme-stack) <- get self, left
     566   var dummy/eax: grapheme <- pop-grapheme-stack left
     567 }
     568
     569 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
     570   var self/eax: (addr gap-buffer) <- copy _self
     571   var right/eax: (addr grapheme-stack) <- get self, right
     572   var result/eax: grapheme <- pop-grapheme-stack right
     573   return result
     574 }
     575
     576 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
     577   var self/esi: (addr gap-buffer) <- copy _self
     578   # complication: graphemes may be multiple bytes
     579   # so don't rely on length
     580   # instead turn the expected result into a stream and arrange to read from it in order
     581   var stream-storage: (stream byte 0x10/capacity)
     582   var expected-stream/ecx: (addr stream byte) <- address stream-storage
     583   write expected-stream, s
     584   # compare left
     585   var left/edx: (addr grapheme-stack) <- get self, left
     586   var result/eax: boolean <- prefix-match? left, expected-stream
     587   compare result, 0/false
     588   {
     589     break-if-!=
     590     return result
     591   }
     592   # compare right
     593   var right/edx: (addr grapheme-stack) <- get self, right
     594   result <- suffix-match? right, expected-stream
     595   compare result, 0/false
     596   {
     597     break-if-!=
     598     return result
     599   }
     600   # ensure there's nothing left over
     601   result <- stream-empty? expected-stream
     602   return result
     603 }
     604
     605 fn test-gap-buffer-equal-from-end {
     606   var _g: gap-buffer
     607   var g/esi: (addr gap-buffer) <- address _g
     608   initialize-gap-buffer g, 0x10
     609   #
     610   add-code-point-at-gap g, 0x61/a
     611   add-code-point-at-gap g, 0x61/a
     612   add-code-point-at-gap g, 0x61/a
     613   # gap is at end (right is empty)
     614   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     615   check result, "F - test-gap-buffer-equal-from-end"
     616 }
     617
     618 fn test-gap-buffer-equal-from-middle {
     619   var _g: gap-buffer
     620   var g/esi: (addr gap-buffer) <- address _g
     621   initialize-gap-buffer g, 0x10
     622   #
     623   add-code-point-at-gap g, 0x61/a
     624   add-code-point-at-gap g, 0x61/a
     625   add-code-point-at-gap g, 0x61/a
     626   var dummy/eax: grapheme <- gap-left g
     627   # gap is in the middle
     628   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     629   check result, "F - test-gap-buffer-equal-from-middle"
     630 }
     631
     632 fn test-gap-buffer-equal-from-start {
     633   var _g: gap-buffer
     634   var g/esi: (addr gap-buffer) <- address _g
     635   initialize-gap-buffer g, 0x10
     636   #
     637   add-code-point-at-gap g, 0x61/a
     638   add-code-point-at-gap g, 0x61/a
     639   add-code-point-at-gap g, 0x61/a
     640   var dummy/eax: grapheme <- gap-left g
     641   dummy <- gap-left g
     642   dummy <- gap-left g
     643   # gap is at the start
     644   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     645   check result, "F - test-gap-buffer-equal-from-start"
     646 }
     647
     648 fn test-gap-buffer-equal-fails {
     649   # g = "aaa"
     650   var _g: gap-buffer
     651   var g/esi: (addr gap-buffer) <- address _g
     652   initialize-gap-buffer g, 0x10
     653   add-code-point-at-gap g, 0x61/a
     654   add-code-point-at-gap g, 0x61/a
     655   add-code-point-at-gap g, 0x61/a
     656   #
     657   var result/eax: boolean <- gap-buffer-equal? g, "aa"
     658   check-not result, "F - test-gap-buffer-equal-fails"
     659 }
     660
     661 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
     662   var tmp/eax: int <- gap-buffer-length self
     663   var len/ecx: int <- copy tmp
     664   var leng/eax: int <- gap-buffer-length g
     665   compare len, leng
     666   {
     667     break-if-=
     668     return 0/false
     669   }
     670   var i/edx: int <- copy 0
     671   {
     672     compare i, len
     673     break-if->=
     674     {
     675       var tmp/eax: grapheme <- gap-index self, i
     676       var curr/ecx: grapheme <- copy tmp
     677       var currg/eax: grapheme <- gap-index g, i
     678       compare curr, currg
     679       break-if-=
     680       return 0/false
     681     }
     682     i <- increment
     683     loop
     684   }
     685   return 1/true
     686 }
     687
     688 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
     689   var self/esi: (addr gap-buffer) <- copy _self
     690   var n/ebx: int <- copy _n
     691   # if n < left->length, index into left
     692   var left/edi: (addr grapheme-stack) <- get self, left
     693   var left-len-a/edx: (addr int) <- get left, top
     694   compare n, *left-len-a
     695   {
     696     break-if->=
     697     var data-ah/eax: (addr handle array grapheme) <- get left, data
     698     var data/eax: (addr array grapheme) <- lookup *data-ah
     699     var result/eax: (addr grapheme) <- index data, n
     700     return *result
     701   }
     702   # shrink n
     703   n <- subtract *left-len-a
     704   # if n < right->length, index into right
     705   var right/edi: (addr grapheme-stack) <- get self, right
     706   var right-len-a/edx: (addr int) <- get right, top
     707   compare n, *right-len-a
     708   {
     709     break-if->=
     710     var data-ah/eax: (addr handle array grapheme) <- get right, data
     711     var data/eax: (addr array grapheme) <- lookup *data-ah
     712     # idx = right->len - n - 1
     713     var idx/ebx: int <- copy n
     714     idx <- subtract *right-len-a
     715     idx <- negate
     716     idx <- subtract 1
     717     var result/eax: (addr grapheme) <- index data, idx
     718     return *result
     719   }
     720   # error
     721   abort "gap-index: out of bounds"
     722   return 0
     723 }
     724
     725 fn test-gap-buffers-equal? {
     726   var _a: gap-buffer
     727   var a/esi: (addr gap-buffer) <- address _a
     728   initialize-gap-buffer-with a, "abc"
     729   var _b: gap-buffer
     730   var b/edi: (addr gap-buffer) <- address _b
     731   initialize-gap-buffer-with b, "abc"
     732   var _c: gap-buffer
     733   var c/ebx: (addr gap-buffer) <- address _c
     734   initialize-gap-buffer-with c, "ab"
     735   var _d: gap-buffer
     736   var d/edx: (addr gap-buffer) <- address _d
     737   initialize-gap-buffer-with d, "abd"
     738   #
     739   var result/eax: boolean <- gap-buffers-equal? a, a
     740   check result, "F - test-gap-buffers-equal? - reflexive"
     741   result <- gap-buffers-equal? a, b
     742   check result, "F - test-gap-buffers-equal? - equal"
     743   # length not equal
     744   result <- gap-buffers-equal? a, c
     745   check-not result, "F - test-gap-buffers-equal? - not equal"
     746   # contents not equal
     747   result <- gap-buffers-equal? a, d
     748   check-not result, "F - test-gap-buffers-equal? - not equal 2"
     749   result <- gap-buffers-equal? d, a
     750   check-not result, "F - test-gap-buffers-equal? - not equal 3"
     751 }
     752
     753 fn test-gap-buffer-index {
     754   var gap-storage: gap-buffer
     755   var gap/esi: (addr gap-buffer) <- address gap-storage
     756   initialize-gap-buffer-with gap, "abc"
     757   # gap is at end, all contents are in left
     758   var g/eax: grapheme <- gap-index gap, 0
     759   var x/ecx: int <- copy g
     760   check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
     761   var g/eax: grapheme <- gap-index gap, 1
     762   var x/ecx: int <- copy g
     763   check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
     764   var g/eax: grapheme <- gap-index gap, 2
     765   var x/ecx: int <- copy g
     766   check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
     767   # now check when everything is to the right
     768   gap-to-start gap
     769   rewind-gap-buffer gap
     770   var g/eax: grapheme <- gap-index gap, 0
     771   var x/ecx: int <- copy g
     772   check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
     773   var g/eax: grapheme <- gap-index gap, 1
     774   var x/ecx: int <- copy g
     775   check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
     776   var g/eax: grapheme <- gap-index gap, 2
     777   var x/ecx: int <- copy g
     778   check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
     779 }
     780
     781 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
     782   # obtain src-a, dest-a
     783   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
     784   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
     785   var src-a/esi: (addr gap-buffer) <- copy _src-a
     786   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
     787   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
     788   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
     789   # copy left grapheme-stack
     790   var src/ecx: (addr grapheme-stack) <- get src-a, left
     791   var dest/edx: (addr grapheme-stack) <- get dest-a, left
     792   copy-grapheme-stack src, dest
     793   # copy right grapheme-stack
     794   src <- get src-a, right
     795   dest <- get dest-a, right
     796   copy-grapheme-stack src, dest
     797 }
     798
     799 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
     800   var self/esi: (addr gap-buffer) <- copy _self
     801   var curr/ecx: (addr grapheme-stack) <- get self, left
     802   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
     803   {
     804     compare result, 0/false
     805     break-if-=
     806     curr <- get self, right
     807     result <- grapheme-stack-is-decimal-integer? curr
     808   }
     809   return result
     810 }
     811
     812 fn test-render-gap-buffer-without-cursor {
     813   # setup
     814   var gap-storage: gap-buffer
     815   var gap/esi: (addr gap-buffer) <- address gap-storage
     816   initialize-gap-buffer-with gap, "abc"
     817   # setup: screen
     818   var screen-on-stack: screen
     819   var screen/edi: (addr screen) <- address screen-on-stack
     820   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     821   #
     822   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor, 3/fg, 0xc5/bg=blue-bg
     823   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
     824   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
     825                                                                 # abc
     826   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
     827 }
     828
     829 fn test-render-gap-buffer-with-cursor-at-end {
     830   # setup
     831   var gap-storage: gap-buffer
     832   var gap/esi: (addr gap-buffer) <- address gap-storage
     833   initialize-gap-buffer-with gap, "abc"
     834   gap-to-end gap
     835   # setup: screen
     836   var screen-on-stack: screen
     837   var screen/edi: (addr screen) <- address screen-on-stack
     838   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     839   #
     840   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     841   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
     842   # we've drawn one extra grapheme for the cursor
     843   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
     844                                                                 # abc
     845   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
     846 }
     847
     848 fn test-render-gap-buffer-with-cursor-in-middle {
     849   # setup
     850   var gap-storage: gap-buffer
     851   var gap/esi: (addr gap-buffer) <- address gap-storage
     852   initialize-gap-buffer-with gap, "abc"
     853   gap-to-end gap
     854   var dummy/eax: grapheme <- gap-left gap
     855   # setup: screen
     856   var screen-on-stack: screen
     857   var screen/edi: (addr screen) <- address screen-on-stack
     858   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     859   #
     860   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     861   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
     862   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
     863                                                                 # abc
     864   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
     865 }
     866
     867 fn test-render-gap-buffer-with-cursor-at-start {
     868   var gap-storage: gap-buffer
     869   var gap/esi: (addr gap-buffer) <- address gap-storage
     870   initialize-gap-buffer-with gap, "abc"
     871   gap-to-start gap
     872   # setup: screen
     873   var screen-on-stack: screen
     874   var screen/edi: (addr screen) <- address screen-on-stack
     875   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     876   #
     877   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     878   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
     879   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
     880                                                                 # abc
     881   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
     882 }
     883
     884 fn test-render-gap-buffer-highlight-matching-close-paren {
     885   var gap-storage: gap-buffer
     886   var gap/esi: (addr gap-buffer) <- address gap-storage
     887   initialize-gap-buffer-with gap, "(a)"
     888   gap-to-start gap
     889   # setup: screen
     890   var screen-on-stack: screen
     891   var screen/edi: (addr screen) <- address screen-on-stack
     892   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     893   #
     894   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     895   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-close-paren"
     896   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-close-paren: result"
     897   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "|   ", "F - test-render-gap-buffer-highlight-matching-close-paren: cursor"
     898   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "  ) ", "F - test-render-gap-buffer-highlight-matching-close-paren: matching paren"
     899 }
     900
     901 fn test-render-gap-buffer-highlight-matching-open-paren {
     902   var gap-storage: gap-buffer
     903   var gap/esi: (addr gap-buffer) <- address gap-storage
     904   initialize-gap-buffer-with gap, "(a)"
     905   gap-to-end gap
     906   var dummy/eax: grapheme <- gap-left gap
     907   # setup: screen
     908   var screen-on-stack: screen
     909   var screen/edi: (addr screen) <- address screen-on-stack
     910   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     911   #
     912   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     913   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren"
     914   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren: result"
     915   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "  | ", "F - test-render-gap-buffer-highlight-matching-open-paren: cursor"
     916   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren: matching paren"
     917 }
     918
     919 fn test-render-gap-buffer-highlight-matching-open-paren-of-end {
     920   var gap-storage: gap-buffer
     921   var gap/esi: (addr gap-buffer) <- address gap-storage
     922   initialize-gap-buffer-with gap, "(a)"
     923   gap-to-end gap
     924   # setup: screen
     925   var screen-on-stack: screen
     926   var screen/edi: (addr screen) <- address screen-on-stack
     927   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     928   #
     929   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     930   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end"
     931   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: result"
     932   check-background-color-in-screen-row screen, 7/bg=cursor,      0/y, "   |", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: cursor"
     933   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: matching paren"
     934 }
     935
     936 # should I highlight a matching open paren? And if so, at what depth from top of left?
     937 # basically there are two cases to disambiguate here:
     938 #   Usually the cursor is at top of right. Highlight first '(' at depth 0 from top of left.
     939 #   If right is empty, match the ')' _before_ cursor. Highlight first '(' at depth _1_ from top of left.
     940 fn highlight-matching-open-paren? _gap: (addr gap-buffer), render-cursor?: boolean -> _/ebx: boolean, _/edi: int {
     941   # if not rendering cursor, return
     942   compare render-cursor?, 0/false
     943   {
     944     break-if-!=
     945     return 0/false, 0
     946   }
     947   var gap/esi: (addr gap-buffer) <- copy _gap
     948   var stack/edi: (addr grapheme-stack) <- get gap, right
     949   var top-addr/eax: (addr int) <- get stack, top
     950   var top-index/ecx: int <- copy *top-addr
     951   compare top-index, 0
     952   {
     953     break-if->
     954     # if cursor at end, return (char before cursor == ')', 1)
     955     stack <- get gap, left
     956     top-addr <- get stack, top
     957     top-index <- copy *top-addr
     958     compare top-index, 0
     959     {
     960       break-if->
     961       return 0/false, 0
     962     }
     963     top-index <- decrement
     964     var data-ah/eax: (addr handle array grapheme) <- get stack, data
     965     var data/eax: (addr array grapheme) <- lookup *data-ah
     966     var g/eax: (addr grapheme) <- index data, top-index
     967     compare *g, 0x29/close-paren
     968     {
     969       break-if-=
     970       return 0/false, 0
     971     }
     972     return 1/true, 1
     973   }
     974   # cursor is not at end; return (char at cursor == ')')
     975   top-index <- decrement
     976   var data-ah/eax: (addr handle array grapheme) <- get stack, data
     977   var data/eax: (addr array grapheme) <- lookup *data-ah
     978   var g/eax: (addr grapheme) <- index data, top-index
     979   compare *g, 0x29/close-paren
     980   {
     981     break-if-=
     982     return 0/false, 0
     983   }
     984   return 1/true, 0
     985 }
     986
     987 fn test-highlight-matching-open-paren {
     988   var gap-storage: gap-buffer
     989   var gap/esi: (addr gap-buffer) <- address gap-storage
     990   initialize-gap-buffer-with gap, "(a)"
     991   gap-to-end gap
     992   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
     993   var open-paren-depth/edi: int <- copy 0
     994   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 0/no-cursor
     995   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: no cursor"
     996   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
     997   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: at end immediately after ')'"
     998   check-ints-equal open-paren-depth, 1, "F - test-highlight-matching-open-paren: depth at end immediately after ')'"
     999   var dummy/eax: grapheme <- gap-left gap
    1000   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
    1001   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: on ')'"
    1002   dummy <- gap-left gap
    1003   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
    1004   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: not on ')'"
    1005 }
    1006
    1007 ## some primitives for scanning through a gap buffer
    1008 # don't modify the gap buffer while scanning
    1009 # this includes moving the cursor around
    1010
    1011 # restart scan without affecting gap-buffer contents
    1012 fn rewind-gap-buffer _self: (addr gap-buffer) {
    1013   var self/esi: (addr gap-buffer) <- copy _self
    1014   var dest/eax: (addr int) <- get self, left-read-index
    1015   copy-to *dest, 0
    1016   dest <- get self, right-read-index
    1017   copy-to *dest, 0
    1018 }
    1019
    1020 fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
    1021   var self/esi: (addr gap-buffer) <- copy _self
    1022   # more in left?
    1023   var left/eax: (addr grapheme-stack) <- get self, left
    1024   var left-size/eax: int <- grapheme-stack-length left
    1025   var left-read-index/ecx: (addr int) <- get self, left-read-index
    1026   compare *left-read-index, left-size
    1027   {
    1028     break-if->=
    1029     return 0/false
    1030   }
    1031   # more in right?
    1032   var right/eax: (addr grapheme-stack) <- get self, right
    1033   var right-size/eax: int <- grapheme-stack-length right
    1034   var right-read-index/ecx: (addr int) <- get self, right-read-index
    1035   compare *right-read-index, right-size
    1036   {
    1037     break-if->=
    1038     return 0/false
    1039   }
    1040   #
    1041   return 1/true
    1042 }
    1043
    1044 fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
    1045   var self/esi: (addr gap-buffer) <- copy _self
    1046   # more in left?
    1047   var left/ecx: (addr grapheme-stack) <- get self, left
    1048   var left-size/eax: int <- grapheme-stack-length left
    1049   var left-read-index-a/edx: (addr int) <- get self, left-read-index
    1050   compare *left-read-index-a, left-size
    1051   {
    1052     break-if->=
    1053     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
    1054     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
    1055     var left-read-index/ecx: int <- copy *left-read-index-a
    1056     var result/eax: (addr grapheme) <- index left-data, left-read-index
    1057     return *result
    1058   }
    1059   # more in right?
    1060   var right/ecx: (addr grapheme-stack) <- get self, right
    1061   var _right-size/eax: int <- grapheme-stack-length right
    1062   var right-size/ebx: int <- copy _right-size
    1063   var right-read-index-a/edx: (addr int) <- get self, right-read-index
    1064   compare *right-read-index-a, right-size
    1065   {
    1066     break-if->=
    1067     # read the right from reverse
    1068     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
    1069     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
    1070     var right-read-index/ebx: int <- copy right-size
    1071     right-read-index <- subtract *right-read-index-a
    1072     right-read-index <- subtract 1
    1073     var result/eax: (addr grapheme) <- index right-data, right-read-index
    1074     return *result
    1075   }
    1076   # if we get here there's nothing left
    1077   return 0/nul
    1078 }
    1079
    1080 fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
    1081   var self/esi: (addr gap-buffer) <- copy _self
    1082   # more in left?
    1083   var left/ecx: (addr grapheme-stack) <- get self, left
    1084   var left-size/eax: int <- grapheme-stack-length left
    1085   var left-read-index-a/edx: (addr int) <- get self, left-read-index
    1086   compare *left-read-index-a, left-size
    1087   {
    1088     break-if->=
    1089     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
    1090     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
    1091     var left-read-index/ecx: int <- copy *left-read-index-a
    1092     var result/eax: (addr grapheme) <- index left-data, left-read-index
    1093     increment *left-read-index-a
    1094     return *result
    1095   }
    1096   # more in right?
    1097   var right/ecx: (addr grapheme-stack) <- get self, right
    1098   var _right-size/eax: int <- grapheme-stack-length right
    1099   var right-size/ebx: int <- copy _right-size
    1100   var right-read-index-a/edx: (addr int) <- get self, right-read-index
    1101   compare *right-read-index-a, right-size
    1102   {
    1103     break-if->=
    1104     # read the right from reverse
    1105     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
    1106     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
    1107     var right-read-index/ebx: int <- copy right-size
    1108     right-read-index <- subtract *right-read-index-a
    1109     right-read-index <- subtract 1
    1110     var result/eax: (addr grapheme) <- index right-data, right-read-index
    1111     increment *right-read-index-a
    1112     return *result
    1113   }
    1114   # if we get here there's nothing left
    1115   return 0/nul
    1116 }
    1117
    1118 fn test-read-from-gap-buffer {
    1119   var gap-storage: gap-buffer
    1120   var gap/esi: (addr gap-buffer) <- address gap-storage
    1121   initialize-gap-buffer-with gap, "abc"
    1122   # gap is at end, all contents are in left
    1123   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1124   check-not done?, "F - test-read-from-gap-buffer/left-1/done"
    1125   var g/eax: grapheme <- read-from-gap-buffer gap
    1126   var x/ecx: int <- copy g
    1127   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
    1128   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1129   check-not done?, "F - test-read-from-gap-buffer/left-2/done"
    1130   var g/eax: grapheme <- read-from-gap-buffer gap
    1131   var x/ecx: int <- copy g
    1132   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
    1133   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1134   check-not done?, "F - test-read-from-gap-buffer/left-3/done"
    1135   var g/eax: grapheme <- read-from-gap-buffer gap
    1136   var x/ecx: int <- copy g
    1137   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
    1138   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1139   check done?, "F - test-read-from-gap-buffer/left-4/done"
    1140   var g/eax: grapheme <- read-from-gap-buffer gap
    1141   var x/ecx: int <- copy g
    1142   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
    1143   # now check when everything is to the right
    1144   gap-to-start gap
    1145   rewind-gap-buffer gap
    1146   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1147   check-not done?, "F - test-read-from-gap-buffer/right-1/done"
    1148   var g/eax: grapheme <- read-from-gap-buffer gap
    1149   var x/ecx: int <- copy g
    1150   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
    1151   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1152   check-not done?, "F - test-read-from-gap-buffer/right-2/done"
    1153   var g/eax: grapheme <- read-from-gap-buffer gap
    1154   var x/ecx: int <- copy g
    1155   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
    1156   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1157   check-not done?, "F - test-read-from-gap-buffer/right-3/done"
    1158   var g/eax: grapheme <- read-from-gap-buffer gap
    1159   var x/ecx: int <- copy g
    1160   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
    1161   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1162   check done?, "F - test-read-from-gap-buffer/right-4/done"
    1163   var g/eax: grapheme <- read-from-gap-buffer gap
    1164   var x/ecx: int <- copy g
    1165   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
    1166 }
    1167
    1168 fn skip-whitespace-from-gap-buffer self: (addr gap-buffer) {
    1169   var done?/eax: boolean <- gap-buffer-scan-done? self
    1170   compare done?, 0/false
    1171   break-if-!=
    1172   var g/eax: grapheme <- peek-from-gap-buffer self
    1173   {
    1174     compare g, 0x20/space
    1175     break-if-=
    1176     compare g, 0xa/newline
    1177     break-if-=
    1178     return
    1179   }
    1180   g <- read-from-gap-buffer self
    1181   loop
    1182 }
    1183
    1184 fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
    1185   var g/edx: grapheme <- copy key
    1186   {
    1187     compare g, 8/backspace
    1188     break-if-!=
    1189     delete-before-gap self
    1190     return
    1191   }
    1192   {
    1193     compare g, 0x80/left-arrow
    1194     break-if-!=
    1195     var dummy/eax: grapheme <- gap-left self
    1196     return
    1197   }
    1198   {
    1199     compare g, 0x83/right-arrow
    1200     break-if-!=
    1201     var dummy/eax: grapheme <- gap-right self
    1202     return
    1203   }
    1204   {
    1205     compare g, 6/ctrl-f
    1206     break-if-!=
    1207     gap-to-start-of-next-word self
    1208     return
    1209   }
    1210   {
    1211     compare g, 2/ctrl-b
    1212     break-if-!=
    1213     gap-to-end-of-previous-word self
    1214     return
    1215   }
    1216   {
    1217     compare g, 1/ctrl-a
    1218     break-if-!=
    1219     gap-to-previous-start-of-line self
    1220     return
    1221   }
    1222   {
    1223     compare g, 5/ctrl-e
    1224     break-if-!=
    1225     gap-to-next-end-of-line self
    1226     return
    1227   }
    1228   {
    1229     compare g, 0x81/down-arrow
    1230     break-if-!=
    1231     gap-down self
    1232     return
    1233   }
    1234   {
    1235     compare g, 0x82/up-arrow
    1236     break-if-!=
    1237     gap-up self
    1238     return
    1239   }
    1240   {
    1241     compare g, 0x15/ctrl-u
    1242     break-if-!=
    1243     clear-gap-buffer self
    1244     return
    1245   }
    1246   {
    1247     compare g, 9/tab
    1248     break-if-!=
    1249     # tab = 2 spaces
    1250     add-code-point-at-gap self, 0x20/space
    1251     add-code-point-at-gap self, 0x20/space
    1252     return
    1253   }
    1254   # default: insert character
    1255   add-grapheme-at-gap self, g
    1256 }
    1257
    1258 fn gap-to-start-of-next-word self: (addr gap-buffer) {
    1259   var curr/eax: grapheme <- copy 0
    1260   # skip to next space
    1261   {
    1262     curr <- gap-right self
    1263     compare curr, -1
    1264     break-if-=
    1265     compare curr, 0x20/space
    1266     break-if-=
    1267     compare curr, 0xa/newline
    1268     break-if-=
    1269     loop
    1270   }
    1271   # skip past spaces
    1272   {
    1273     curr <- gap-right self
    1274     compare curr, -1
    1275     break-if-=
    1276     compare curr, 0x20/space
    1277     loop-if-=
    1278     compare curr, 0xa/space
    1279     loop-if-=
    1280     curr <- gap-left self
    1281     break
    1282   }
    1283 }
    1284
    1285 fn gap-to-end-of-previous-word self: (addr gap-buffer) {
    1286   var curr/eax: grapheme <- copy 0
    1287   # skip to previous space
    1288   {
    1289     curr <- gap-left self
    1290     compare curr, -1
    1291     break-if-=
    1292     compare curr, 0x20/space
    1293     break-if-=
    1294     compare curr, 0xa/newline
    1295     break-if-=
    1296     loop
    1297   }
    1298   # skip past all spaces but one
    1299   {
    1300     curr <- gap-left self
    1301     compare curr, -1
    1302     break-if-=
    1303     compare curr, 0x20/space
    1304     loop-if-=
    1305     compare curr, 0xa/space
    1306     loop-if-=
    1307     curr <- gap-right self
    1308     break
    1309   }
    1310 }
    1311
    1312 fn gap-to-previous-start-of-line self: (addr gap-buffer) {
    1313   # skip past immediate newline
    1314   var dummy/eax: grapheme <- gap-left self
    1315   # skip to previous newline
    1316   {
    1317     dummy <- gap-left self
    1318     {
    1319       compare dummy, -1
    1320       break-if-!=
    1321       return
    1322     }
    1323     {
    1324       compare dummy, 0xa/newline
    1325       break-if-!=
    1326       dummy <- gap-right self
    1327       return
    1328     }
    1329     loop
    1330   }
    1331 }
    1332
    1333 fn gap-to-next-end-of-line self: (addr gap-buffer) {
    1334   # skip past immediate newline
    1335   var dummy/eax: grapheme <- gap-right self
    1336   # skip to next newline
    1337   {
    1338     dummy <- gap-right self
    1339     {
    1340       compare dummy, -1
    1341       break-if-!=
    1342       return
    1343     }
    1344     {
    1345       compare dummy, 0xa/newline
    1346       break-if-!=
    1347       dummy <- gap-left self
    1348       return
    1349     }
    1350     loop
    1351   }
    1352 }
    1353
    1354 fn gap-up self: (addr gap-buffer) {
    1355   # compute column
    1356   var col/edx: int <- count-columns-to-start-of-line self
    1357   #
    1358   gap-to-previous-start-of-line self
    1359   # skip ahead by up to col on previous line
    1360   var i/ecx: int <- copy 0
    1361   {
    1362     compare i, col
    1363     break-if->=
    1364     var curr/eax: grapheme <- gap-right self
    1365     {
    1366       compare curr, -1
    1367       break-if-!=
    1368       return
    1369     }
    1370     compare curr, 0xa/newline
    1371     {
    1372       break-if-!=
    1373       curr <- gap-left self
    1374       return
    1375     }
    1376     i <- increment
    1377     loop
    1378   }
    1379 }
    1380
    1381 fn gap-down self: (addr gap-buffer) {
    1382   # compute column
    1383   var col/edx: int <- count-columns-to-start-of-line self
    1384   # skip to start of next line
    1385   gap-to-end-of-line self
    1386   var dummy/eax: grapheme <- gap-right self
    1387   # skip ahead by up to col on previous line
    1388   var i/ecx: int <- copy 0
    1389   {
    1390     compare i, col
    1391     break-if->=
    1392     var curr/eax: grapheme <- gap-right self
    1393     {
    1394       compare curr, -1
    1395       break-if-!=
    1396       return
    1397     }
    1398     compare curr, 0xa/newline
    1399     {
    1400       break-if-!=
    1401       curr <- gap-left self
    1402       return
    1403     }
    1404     i <- increment
    1405     loop
    1406   }
    1407 }
    1408
    1409 fn count-columns-to-start-of-line self: (addr gap-buffer) -> _/edx: int {
    1410   var count/edx: int <- copy 0
    1411   var dummy/eax: grapheme <- copy 0
    1412   # skip to previous newline
    1413   {
    1414     dummy <- gap-left self
    1415     {
    1416       compare dummy, -1
    1417       break-if-!=
    1418       return count
    1419     }
    1420     {
    1421       compare dummy, 0xa/newline
    1422       break-if-!=
    1423       dummy <- gap-right self
    1424       return count
    1425     }
    1426     count <- increment
    1427     loop
    1428   }
    1429   return count
    1430 }
    1431
    1432 fn gap-to-end-of-line self: (addr gap-buffer) {
    1433   var dummy/eax: grapheme <- copy 0
    1434   # skip to next newline
    1435   {
    1436     dummy <- gap-right self
    1437     {
    1438       compare dummy, -1
    1439       break-if-!=
    1440       return
    1441     }
    1442     {
    1443       compare dummy, 0xa/newline
    1444       break-if-!=
    1445       dummy <- gap-left self
    1446       return
    1447     }
    1448     loop
    1449   }
    1450 }