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) -> _/edx: 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), keys: (addr array byte) {
      54   initialize-gap-buffer self, 0x40/capacity
      55   var input-stream-storage: (stream byte 0x40/capacity)
      56   var input-stream/ecx: (addr stream byte) <- address input-stream-storage
      57   write input-stream, keys
      58   {
      59     var done?/eax: boolean <- stream-empty? input-stream
      60     compare done?, 0/false
      61     break-if-!=
      62     var g/eax: grapheme <- read-grapheme input-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 fg/edi: int <- copy color
     399   var bg/ebx: int <- copy background-color
     400   compare render-cursor?, 0/false
     401   {
     402     break-if-=
     403     # if the right side is empty, grapheme stack didn't print the cursor
     404     var empty?/eax: boolean <- grapheme-stack-empty? right
     405     compare empty?, 0/false
     406     break-if-=
     407     # swap foreground and background
     408     fg <- copy background-color
     409     bg <- copy color
     410   }
     411   # print a grapheme either way so that cursor position doesn't affect printed width
     412   var space/edx: grapheme <- copy 0x20
     413   x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, fg, bg
     414   return x2, y2
     415 }
     416
     417 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 {
     418   var _width/eax: int <- copy 0
     419   var _height/ecx: int <- copy 0
     420   _width, _height <- screen-size screen
     421   var width/edx: int <- copy _width
     422   var height/ebx: int <- copy _height
     423   var x2/eax: int <- copy 0
     424   var y2/ecx: int <- copy 0
     425   x2, y2 <- render-gap-buffer-wrapping-right-then-down screen, gap, x, y, width, height, render-cursor?, color, background-color
     426   return x2  # y2? yolo
     427 }
     428
     429 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
     430   var gap/esi: (addr gap-buffer) <- copy _gap
     431   var left/eax: (addr grapheme-stack) <- get gap, left
     432   var tmp/eax: (addr int) <- get left, top
     433   var left-length/ecx: int <- copy *tmp
     434   var right/esi: (addr grapheme-stack) <- get gap, right
     435   tmp <- get right, top
     436   var result/eax: int <- copy *tmp
     437   result <- add left-length
     438   return result
     439 }
     440
     441 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
     442   var self/esi: (addr gap-buffer) <- copy _self
     443   var left/eax: (addr grapheme-stack) <- get self, left
     444   push-grapheme-stack left, g
     445 }
     446
     447 fn add-code-point-at-gap self: (addr gap-buffer), c: code-point {
     448   var g/eax: grapheme <- copy c
     449   add-grapheme-at-gap self, g
     450 }
     451
     452 fn gap-to-start self: (addr gap-buffer) {
     453   {
     454     var curr/eax: grapheme <- gap-left self
     455     compare curr, -1
     456     loop-if-!=
     457   }
     458 }
     459
     460 fn gap-to-end self: (addr gap-buffer) {
     461   {
     462     var curr/eax: grapheme <- gap-right self
     463     compare curr, -1
     464     loop-if-!=
     465   }
     466 }
     467
     468 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
     469   var self/esi: (addr gap-buffer) <- copy _self
     470   var left/eax: (addr grapheme-stack) <- get self, left
     471   var result/eax: boolean <- grapheme-stack-empty? left
     472   return result
     473 }
     474
     475 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
     476   var self/esi: (addr gap-buffer) <- copy _self
     477   var right/eax: (addr grapheme-stack) <- get self, right
     478   var result/eax: boolean <- grapheme-stack-empty? right
     479   return result
     480 }
     481
     482 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
     483   var self/esi: (addr gap-buffer) <- copy _self
     484   var g/eax: grapheme <- copy 0
     485   var right/ecx: (addr grapheme-stack) <- get self, right
     486   g <- pop-grapheme-stack right
     487   compare g, -1
     488   {
     489     break-if-=
     490     var left/ecx: (addr grapheme-stack) <- get self, left
     491     push-grapheme-stack left, g
     492   }
     493   return g
     494 }
     495
     496 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
     497   var self/esi: (addr gap-buffer) <- copy _self
     498   var g/eax: grapheme <- copy 0
     499   {
     500     var left/ecx: (addr grapheme-stack) <- get self, left
     501     g <- pop-grapheme-stack left
     502   }
     503   compare g, -1
     504   {
     505     break-if-=
     506     var right/ecx: (addr grapheme-stack) <- get self, right
     507     push-grapheme-stack right, g
     508   }
     509   return g
     510 }
     511
     512 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
     513   var self/eax: (addr gap-buffer) <- copy _self
     514   var left/eax: (addr grapheme-stack) <- get self, left
     515   var top-addr/eax: (addr int) <- get left, top
     516   var result/eax: int <- copy *top-addr
     517   return result
     518 }
     519
     520 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     521   var self/esi: (addr gap-buffer) <- copy _self
     522   # try to read from left
     523   var left/eax: (addr grapheme-stack) <- get self, left
     524   var top-addr/ecx: (addr int) <- get left, top
     525   compare *top-addr, 0
     526   {
     527     break-if-<=
     528     var data-ah/eax: (addr handle array grapheme) <- get left, data
     529     var data/eax: (addr array grapheme) <- lookup *data-ah
     530     var result-addr/eax: (addr grapheme) <- index data, 0
     531     return *result-addr
     532   }
     533   # try to read from right
     534   var right/eax: (addr grapheme-stack) <- get self, right
     535   top-addr <- get right, top
     536   compare *top-addr, 0
     537   {
     538     break-if-<=
     539     var data-ah/eax: (addr handle array grapheme) <- get right, data
     540     var data/eax: (addr array grapheme) <- lookup *data-ah
     541     var top/ecx: int <- copy *top-addr
     542     top <- decrement
     543     var result-addr/eax: (addr grapheme) <- index data, top
     544     return *result-addr
     545   }
     546   # give up
     547   return -1
     548 }
     549
     550 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
     551   var self/esi: (addr gap-buffer) <- copy _self
     552   # try to read from left
     553   var left/ecx: (addr grapheme-stack) <- get self, left
     554   var top-addr/edx: (addr int) <- get left, top
     555   compare *top-addr, 0
     556   {
     557     break-if-<=
     558     var result/eax: grapheme <- pop-grapheme-stack left
     559     push-grapheme-stack left, result
     560     return result
     561   }
     562   # give up
     563   return -1
     564 }
     565
     566 fn delete-before-gap _self: (addr gap-buffer) {
     567   var self/eax: (addr gap-buffer) <- copy _self
     568   var left/eax: (addr grapheme-stack) <- get self, left
     569   var dummy/eax: grapheme <- pop-grapheme-stack left
     570 }
     571
     572 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
     573   var self/eax: (addr gap-buffer) <- copy _self
     574   var right/eax: (addr grapheme-stack) <- get self, right
     575   var result/eax: grapheme <- pop-grapheme-stack right
     576   return result
     577 }
     578
     579 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
     580   var self/esi: (addr gap-buffer) <- copy _self
     581   # complication: graphemes may be multiple bytes
     582   # so don't rely on length
     583   # instead turn the expected result into a stream and arrange to read from it in order
     584   var stream-storage: (stream byte 0x10/capacity)
     585   var expected-stream/ecx: (addr stream byte) <- address stream-storage
     586   write expected-stream, s
     587   # compare left
     588   var left/edx: (addr grapheme-stack) <- get self, left
     589   var result/eax: boolean <- prefix-match? left, expected-stream
     590   compare result, 0/false
     591   {
     592     break-if-!=
     593     return result
     594   }
     595   # compare right
     596   var right/edx: (addr grapheme-stack) <- get self, right
     597   result <- suffix-match? right, expected-stream
     598   compare result, 0/false
     599   {
     600     break-if-!=
     601     return result
     602   }
     603   # ensure there's nothing left over
     604   result <- stream-empty? expected-stream
     605   return result
     606 }
     607
     608 fn test-gap-buffer-equal-from-end {
     609   var _g: gap-buffer
     610   var g/esi: (addr gap-buffer) <- address _g
     611   initialize-gap-buffer g, 0x10
     612   #
     613   add-code-point-at-gap g, 0x61/a
     614   add-code-point-at-gap g, 0x61/a
     615   add-code-point-at-gap g, 0x61/a
     616   # gap is at end (right is empty)
     617   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     618   check result, "F - test-gap-buffer-equal-from-end"
     619 }
     620
     621 fn test-gap-buffer-equal-from-middle {
     622   var _g: gap-buffer
     623   var g/esi: (addr gap-buffer) <- address _g
     624   initialize-gap-buffer g, 0x10
     625   #
     626   add-code-point-at-gap g, 0x61/a
     627   add-code-point-at-gap g, 0x61/a
     628   add-code-point-at-gap g, 0x61/a
     629   var dummy/eax: grapheme <- gap-left g
     630   # gap is in the middle
     631   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     632   check result, "F - test-gap-buffer-equal-from-middle"
     633 }
     634
     635 fn test-gap-buffer-equal-from-start {
     636   var _g: gap-buffer
     637   var g/esi: (addr gap-buffer) <- address _g
     638   initialize-gap-buffer g, 0x10
     639   #
     640   add-code-point-at-gap g, 0x61/a
     641   add-code-point-at-gap g, 0x61/a
     642   add-code-point-at-gap g, 0x61/a
     643   var dummy/eax: grapheme <- gap-left g
     644   dummy <- gap-left g
     645   dummy <- gap-left g
     646   # gap is at the start
     647   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
     648   check result, "F - test-gap-buffer-equal-from-start"
     649 }
     650
     651 fn test-gap-buffer-equal-fails {
     652   # g = "aaa"
     653   var _g: gap-buffer
     654   var g/esi: (addr gap-buffer) <- address _g
     655   initialize-gap-buffer g, 0x10
     656   add-code-point-at-gap g, 0x61/a
     657   add-code-point-at-gap g, 0x61/a
     658   add-code-point-at-gap g, 0x61/a
     659   #
     660   var result/eax: boolean <- gap-buffer-equal? g, "aa"
     661   check-not result, "F - test-gap-buffer-equal-fails"
     662 }
     663
     664 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
     665   var tmp/eax: int <- gap-buffer-length self
     666   var len/ecx: int <- copy tmp
     667   var leng/eax: int <- gap-buffer-length g
     668   compare len, leng
     669   {
     670     break-if-=
     671     return 0/false
     672   }
     673   var i/edx: int <- copy 0
     674   {
     675     compare i, len
     676     break-if->=
     677     {
     678       var tmp/eax: grapheme <- gap-index self, i
     679       var curr/ecx: grapheme <- copy tmp
     680       var currg/eax: grapheme <- gap-index g, i
     681       compare curr, currg
     682       break-if-=
     683       return 0/false
     684     }
     685     i <- increment
     686     loop
     687   }
     688   return 1/true
     689 }
     690
     691 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
     692   var self/esi: (addr gap-buffer) <- copy _self
     693   var n/ebx: int <- copy _n
     694   # if n < left->length, index into left
     695   var left/edi: (addr grapheme-stack) <- get self, left
     696   var left-len-a/edx: (addr int) <- get left, top
     697   compare n, *left-len-a
     698   {
     699     break-if->=
     700     var data-ah/eax: (addr handle array grapheme) <- get left, data
     701     var data/eax: (addr array grapheme) <- lookup *data-ah
     702     var result/eax: (addr grapheme) <- index data, n
     703     return *result
     704   }
     705   # shrink n
     706   n <- subtract *left-len-a
     707   # if n < right->length, index into right
     708   var right/edi: (addr grapheme-stack) <- get self, right
     709   var right-len-a/edx: (addr int) <- get right, top
     710   compare n, *right-len-a
     711   {
     712     break-if->=
     713     var data-ah/eax: (addr handle array grapheme) <- get right, data
     714     var data/eax: (addr array grapheme) <- lookup *data-ah
     715     # idx = right->len - n - 1
     716     var idx/ebx: int <- copy n
     717     idx <- subtract *right-len-a
     718     idx <- negate
     719     idx <- subtract 1
     720     var result/eax: (addr grapheme) <- index data, idx
     721     return *result
     722   }
     723   # error
     724   abort "gap-index: out of bounds"
     725   return 0
     726 }
     727
     728 fn test-gap-buffers-equal? {
     729   var _a: gap-buffer
     730   var a/esi: (addr gap-buffer) <- address _a
     731   initialize-gap-buffer-with a, "abc"
     732   var _b: gap-buffer
     733   var b/edi: (addr gap-buffer) <- address _b
     734   initialize-gap-buffer-with b, "abc"
     735   var _c: gap-buffer
     736   var c/ebx: (addr gap-buffer) <- address _c
     737   initialize-gap-buffer-with c, "ab"
     738   var _d: gap-buffer
     739   var d/edx: (addr gap-buffer) <- address _d
     740   initialize-gap-buffer-with d, "abd"
     741   #
     742   var result/eax: boolean <- gap-buffers-equal? a, a
     743   check result, "F - test-gap-buffers-equal? - reflexive"
     744   result <- gap-buffers-equal? a, b
     745   check result, "F - test-gap-buffers-equal? - equal"
     746   # length not equal
     747   result <- gap-buffers-equal? a, c
     748   check-not result, "F - test-gap-buffers-equal? - not equal"
     749   # contents not equal
     750   result <- gap-buffers-equal? a, d
     751   check-not result, "F - test-gap-buffers-equal? - not equal 2"
     752   result <- gap-buffers-equal? d, a
     753   check-not result, "F - test-gap-buffers-equal? - not equal 3"
     754 }
     755
     756 fn test-gap-buffer-index {
     757   var gap-storage: gap-buffer
     758   var gap/esi: (addr gap-buffer) <- address gap-storage
     759   initialize-gap-buffer-with gap, "abc"
     760   # gap is at end, all contents are in left
     761   var g/eax: grapheme <- gap-index gap, 0
     762   var x/ecx: int <- copy g
     763   check-ints-equal x, 0x61/a, "F - test-gap-index/left-1"
     764   var g/eax: grapheme <- gap-index gap, 1
     765   var x/ecx: int <- copy g
     766   check-ints-equal x, 0x62/b, "F - test-gap-index/left-2"
     767   var g/eax: grapheme <- gap-index gap, 2
     768   var x/ecx: int <- copy g
     769   check-ints-equal x, 0x63/c, "F - test-gap-index/left-3"
     770   # now check when everything is to the right
     771   gap-to-start gap
     772   rewind-gap-buffer gap
     773   var g/eax: grapheme <- gap-index gap, 0
     774   var x/ecx: int <- copy g
     775   check-ints-equal x, 0x61/a, "F - test-gap-index/right-1"
     776   var g/eax: grapheme <- gap-index gap, 1
     777   var x/ecx: int <- copy g
     778   check-ints-equal x, 0x62/b, "F - test-gap-index/right-2"
     779   var g/eax: grapheme <- gap-index gap, 2
     780   var x/ecx: int <- copy g
     781   check-ints-equal x, 0x63/c, "F - test-gap-index/right-3"
     782 }
     783
     784 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
     785   # obtain src-a, dest-a
     786   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
     787   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
     788   var src-a/esi: (addr gap-buffer) <- copy _src-a
     789   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
     790   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
     791   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
     792   # copy left grapheme-stack
     793   var src/ecx: (addr grapheme-stack) <- get src-a, left
     794   var dest/edx: (addr grapheme-stack) <- get dest-a, left
     795   copy-grapheme-stack src, dest
     796   # copy right grapheme-stack
     797   src <- get src-a, right
     798   dest <- get dest-a, right
     799   copy-grapheme-stack src, dest
     800 }
     801
     802 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
     803   var self/esi: (addr gap-buffer) <- copy _self
     804   var curr/ecx: (addr grapheme-stack) <- get self, left
     805   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
     806   {
     807     compare result, 0/false
     808     break-if-=
     809     curr <- get self, right
     810     result <- grapheme-stack-is-decimal-integer? curr
     811   }
     812   return result
     813 }
     814
     815 fn test-render-gap-buffer-without-cursor {
     816   # setup
     817   var gap-storage: gap-buffer
     818   var gap/esi: (addr gap-buffer) <- address gap-storage
     819   initialize-gap-buffer-with gap, "abc"
     820   # setup: screen
     821   var screen-on-stack: screen
     822   var screen/edi: (addr screen) <- address screen-on-stack
     823   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     824   #
     825   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor, 3/fg, 0xc5/bg=blue-bg
     826   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
     827   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
     828                                                                 # abc
     829   check-background-color-in-screen-row screen, 3/bg=reverse, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
     830 }
     831
     832 fn test-render-gap-buffer-with-cursor-at-end {
     833   # setup
     834   var gap-storage: gap-buffer
     835   var gap/esi: (addr gap-buffer) <- address gap-storage
     836   initialize-gap-buffer-with gap, "abc"
     837   gap-to-end gap
     838   # setup: screen
     839   var screen-on-stack: screen
     840   var screen/edi: (addr screen) <- address screen-on-stack
     841   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     842   #
     843   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     844   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
     845   # we've drawn one extra grapheme for the cursor
     846   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
     847                                                                 # abc
     848   check-background-color-in-screen-row screen, 3/bg=reverse, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
     849 }
     850
     851 fn test-render-gap-buffer-with-cursor-in-middle {
     852   # setup
     853   var gap-storage: gap-buffer
     854   var gap/esi: (addr gap-buffer) <- address gap-storage
     855   initialize-gap-buffer-with gap, "abc"
     856   gap-to-end gap
     857   var dummy/eax: grapheme <- gap-left gap
     858   # setup: screen
     859   var screen-on-stack: screen
     860   var screen/edi: (addr screen) <- address screen-on-stack
     861   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     862   #
     863   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     864   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
     865   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
     866                                                                 # abc
     867   check-background-color-in-screen-row screen, 3/bg=reverse, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
     868 }
     869
     870 fn test-render-gap-buffer-with-cursor-at-start {
     871   var gap-storage: gap-buffer
     872   var gap/esi: (addr gap-buffer) <- address gap-storage
     873   initialize-gap-buffer-with gap, "abc"
     874   gap-to-start gap
     875   # setup: screen
     876   var screen-on-stack: screen
     877   var screen/edi: (addr screen) <- address screen-on-stack
     878   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     879   #
     880   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     881   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
     882   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
     883                                                                 # abc
     884   check-background-color-in-screen-row screen, 3/bg=reverse, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
     885 }
     886
     887 fn test-render-gap-buffer-highlight-matching-close-paren {
     888   var gap-storage: gap-buffer
     889   var gap/esi: (addr gap-buffer) <- address gap-storage
     890   initialize-gap-buffer-with gap, "(a)"
     891   gap-to-start gap
     892   # setup: screen
     893   var screen-on-stack: screen
     894   var screen/edi: (addr screen) <- address screen-on-stack
     895   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     896   #
     897   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     898   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-close-paren"
     899   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-close-paren: result"
     900   check-background-color-in-screen-row screen, 3/bg=reverse,      0/y, "|   ", "F - test-render-gap-buffer-highlight-matching-close-paren: cursor"
     901   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "  ) ", "F - test-render-gap-buffer-highlight-matching-close-paren: matching paren"
     902 }
     903
     904 fn test-render-gap-buffer-highlight-matching-open-paren {
     905   var gap-storage: gap-buffer
     906   var gap/esi: (addr gap-buffer) <- address gap-storage
     907   initialize-gap-buffer-with gap, "(a)"
     908   gap-to-end gap
     909   var dummy/eax: grapheme <- gap-left gap
     910   # setup: screen
     911   var screen-on-stack: screen
     912   var screen/edi: (addr screen) <- address screen-on-stack
     913   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     914   #
     915   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     916   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren"
     917   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren: result"
     918   check-background-color-in-screen-row screen, 3/bg=reverse,      0/y, "  | ", "F - test-render-gap-buffer-highlight-matching-open-paren: cursor"
     919   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren: matching paren"
     920 }
     921
     922 fn test-render-gap-buffer-highlight-matching-open-paren-of-end {
     923   var gap-storage: gap-buffer
     924   var gap/esi: (addr gap-buffer) <- address gap-storage
     925   initialize-gap-buffer-with gap, "(a)"
     926   gap-to-end gap
     927   # setup: screen
     928   var screen-on-stack: screen
     929   var screen/edi: (addr screen) <- address screen-on-stack
     930   initialize-screen screen, 5, 4, 0/no-pixel-graphics
     931   #
     932   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor, 3/fg, 0xc5/bg=blue-bg
     933   check-screen-row                     screen, 0/y,                   "(a) ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end"
     934   check-ints-equal x, 4, "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: result"
     935   check-background-color-in-screen-row screen, 3/bg=reverse,      0/y, "   |", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: cursor"
     936   check-screen-row-in-color            screen, 0xf/fg=highlight, 0/y, "(   ", "F - test-render-gap-buffer-highlight-matching-open-paren-of-end: matching paren"
     937 }
     938
     939 # should I highlight a matching open paren? And if so, at what depth from top of left?
     940 # basically there are two cases to disambiguate here:
     941 #   Usually the cursor is at top of right. Highlight first '(' at depth 0 from top of left.
     942 #   If right is empty, match the ')' _before_ cursor. Highlight first '(' at depth _1_ from top of left.
     943 fn highlight-matching-open-paren? _gap: (addr gap-buffer), render-cursor?: boolean -> _/ebx: boolean, _/edi: int {
     944   # if not rendering cursor, return
     945   compare render-cursor?, 0/false
     946   {
     947     break-if-!=
     948     return 0/false, 0
     949   }
     950   var gap/esi: (addr gap-buffer) <- copy _gap
     951   var stack/edi: (addr grapheme-stack) <- get gap, right
     952   var top-addr/eax: (addr int) <- get stack, top
     953   var top-index/ecx: int <- copy *top-addr
     954   compare top-index, 0
     955   {
     956     break-if->
     957     # if cursor at end, return (char before cursor == ')', 1)
     958     stack <- get gap, left
     959     top-addr <- get stack, top
     960     top-index <- copy *top-addr
     961     compare top-index, 0
     962     {
     963       break-if->
     964       return 0/false, 0
     965     }
     966     top-index <- decrement
     967     var data-ah/eax: (addr handle array grapheme) <- get stack, data
     968     var data/eax: (addr array grapheme) <- lookup *data-ah
     969     var g/eax: (addr grapheme) <- index data, top-index
     970     compare *g, 0x29/close-paren
     971     {
     972       break-if-=
     973       return 0/false, 0
     974     }
     975     return 1/true, 1
     976   }
     977   # cursor is not at end; return (char at cursor == ')')
     978   top-index <- decrement
     979   var data-ah/eax: (addr handle array grapheme) <- get stack, data
     980   var data/eax: (addr array grapheme) <- lookup *data-ah
     981   var g/eax: (addr grapheme) <- index data, top-index
     982   compare *g, 0x29/close-paren
     983   {
     984     break-if-=
     985     return 0/false, 0
     986   }
     987   return 1/true, 0
     988 }
     989
     990 fn test-highlight-matching-open-paren {
     991   var gap-storage: gap-buffer
     992   var gap/esi: (addr gap-buffer) <- address gap-storage
     993   initialize-gap-buffer-with gap, "(a)"
     994   gap-to-end gap
     995   var highlight-matching-open-paren?/ebx: boolean <- copy 0/false
     996   var open-paren-depth/edi: int <- copy 0
     997   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 0/no-cursor
     998   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: no cursor"
     999   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
    1000   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: at end immediately after ')'"
    1001   check-ints-equal open-paren-depth, 1, "F - test-highlight-matching-open-paren: depth at end immediately after ')'"
    1002   var dummy/eax: grapheme <- gap-left gap
    1003   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
    1004   check highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: on ')'"
    1005   dummy <- gap-left gap
    1006   highlight-matching-open-paren?, open-paren-depth <- highlight-matching-open-paren? gap, 1/render-cursor
    1007   check-not highlight-matching-open-paren?, "F - test-highlight-matching-open-paren: not on ')'"
    1008 }
    1009
    1010 ## some primitives for scanning through a gap buffer
    1011 # don't modify the gap buffer while scanning
    1012 # this includes moving the cursor around
    1013
    1014 # restart scan without affecting gap-buffer contents
    1015 fn rewind-gap-buffer _self: (addr gap-buffer) {
    1016   var self/esi: (addr gap-buffer) <- copy _self
    1017   var dest/eax: (addr int) <- get self, left-read-index
    1018   copy-to *dest, 0
    1019   dest <- get self, right-read-index
    1020   copy-to *dest, 0
    1021 }
    1022
    1023 fn gap-buffer-scan-done? _self: (addr gap-buffer) -> _/eax: boolean {
    1024   var self/esi: (addr gap-buffer) <- copy _self
    1025   # more in left?
    1026   var left/eax: (addr grapheme-stack) <- get self, left
    1027   var left-size/eax: int <- grapheme-stack-length left
    1028   var left-read-index/ecx: (addr int) <- get self, left-read-index
    1029   compare *left-read-index, left-size
    1030   {
    1031     break-if->=
    1032     return 0/false
    1033   }
    1034   # more in right?
    1035   var right/eax: (addr grapheme-stack) <- get self, right
    1036   var right-size/eax: int <- grapheme-stack-length right
    1037   var right-read-index/ecx: (addr int) <- get self, right-read-index
    1038   compare *right-read-index, right-size
    1039   {
    1040     break-if->=
    1041     return 0/false
    1042   }
    1043   #
    1044   return 1/true
    1045 }
    1046
    1047 fn peek-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
    1048   var self/esi: (addr gap-buffer) <- copy _self
    1049   # more in left?
    1050   var left/ecx: (addr grapheme-stack) <- get self, left
    1051   var left-size/eax: int <- grapheme-stack-length left
    1052   var left-read-index-a/edx: (addr int) <- get self, left-read-index
    1053   compare *left-read-index-a, left-size
    1054   {
    1055     break-if->=
    1056     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
    1057     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
    1058     var left-read-index/ecx: int <- copy *left-read-index-a
    1059     var result/eax: (addr grapheme) <- index left-data, left-read-index
    1060     return *result
    1061   }
    1062   # more in right?
    1063   var right/ecx: (addr grapheme-stack) <- get self, right
    1064   var _right-size/eax: int <- grapheme-stack-length right
    1065   var right-size/ebx: int <- copy _right-size
    1066   var right-read-index-a/edx: (addr int) <- get self, right-read-index
    1067   compare *right-read-index-a, right-size
    1068   {
    1069     break-if->=
    1070     # read the right from reverse
    1071     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
    1072     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
    1073     var right-read-index/ebx: int <- copy right-size
    1074     right-read-index <- subtract *right-read-index-a
    1075     right-read-index <- subtract 1
    1076     var result/eax: (addr grapheme) <- index right-data, right-read-index
    1077     return *result
    1078   }
    1079   # if we get here there's nothing left
    1080   return 0/nul
    1081 }
    1082
    1083 fn read-from-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
    1084   var self/esi: (addr gap-buffer) <- copy _self
    1085   # more in left?
    1086   var left/ecx: (addr grapheme-stack) <- get self, left
    1087   var left-size/eax: int <- grapheme-stack-length left
    1088   var left-read-index-a/edx: (addr int) <- get self, left-read-index
    1089   compare *left-read-index-a, left-size
    1090   {
    1091     break-if->=
    1092     var left-data-ah/eax: (addr handle array grapheme) <- get left, data
    1093     var left-data/eax: (addr array grapheme) <- lookup *left-data-ah
    1094     var left-read-index/ecx: int <- copy *left-read-index-a
    1095     var result/eax: (addr grapheme) <- index left-data, left-read-index
    1096     increment *left-read-index-a
    1097     return *result
    1098   }
    1099   # more in right?
    1100   var right/ecx: (addr grapheme-stack) <- get self, right
    1101   var _right-size/eax: int <- grapheme-stack-length right
    1102   var right-size/ebx: int <- copy _right-size
    1103   var right-read-index-a/edx: (addr int) <- get self, right-read-index
    1104   compare *right-read-index-a, right-size
    1105   {
    1106     break-if->=
    1107     # read the right from reverse
    1108     var right-data-ah/eax: (addr handle array grapheme) <- get right, data
    1109     var right-data/eax: (addr array grapheme) <- lookup *right-data-ah
    1110     var right-read-index/ebx: int <- copy right-size
    1111     right-read-index <- subtract *right-read-index-a
    1112     right-read-index <- subtract 1
    1113     var result/eax: (addr grapheme) <- index right-data, right-read-index
    1114     increment *right-read-index-a
    1115     return *result
    1116   }
    1117   # if we get here there's nothing left
    1118   return 0/nul
    1119 }
    1120
    1121 fn put-back-from-gap-buffer _self: (addr gap-buffer) {
    1122   var self/esi: (addr gap-buffer) <- copy _self
    1123   # more in right?
    1124   var right/eax: (addr grapheme-stack) <- get self, right
    1125   var right-size/eax: int <- grapheme-stack-length right
    1126   var right-read-index-a/eax: (addr int) <- get self, right-read-index
    1127   compare *right-read-index-a, 0
    1128   {
    1129     break-if-<=
    1130     decrement *right-read-index-a
    1131     return
    1132   }
    1133   # more in left?
    1134   var left/eax: (addr grapheme-stack) <- get self, left
    1135   var left-size/eax: int <- grapheme-stack-length left
    1136   var left-read-index-a/eax: (addr int) <- get self, left-read-index
    1137   decrement *left-read-index-a
    1138 }
    1139
    1140 fn test-read-from-gap-buffer {
    1141   var gap-storage: gap-buffer
    1142   var gap/esi: (addr gap-buffer) <- address gap-storage
    1143   initialize-gap-buffer-with gap, "abc"
    1144   # gap is at end, all contents are in left
    1145   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1146   check-not done?, "F - test-read-from-gap-buffer/left-1/done"
    1147   var g/eax: grapheme <- read-from-gap-buffer gap
    1148   var x/ecx: int <- copy g
    1149   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/left-1"
    1150   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1151   check-not done?, "F - test-read-from-gap-buffer/left-2/done"
    1152   var g/eax: grapheme <- read-from-gap-buffer gap
    1153   var x/ecx: int <- copy g
    1154   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/left-2"
    1155   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1156   check-not done?, "F - test-read-from-gap-buffer/left-3/done"
    1157   var g/eax: grapheme <- read-from-gap-buffer gap
    1158   var x/ecx: int <- copy g
    1159   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/left-3"
    1160   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1161   check done?, "F - test-read-from-gap-buffer/left-4/done"
    1162   var g/eax: grapheme <- read-from-gap-buffer gap
    1163   var x/ecx: int <- copy g
    1164   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/left-4"
    1165   # now check when everything is to the right
    1166   gap-to-start gap
    1167   rewind-gap-buffer gap
    1168   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1169   check-not done?, "F - test-read-from-gap-buffer/right-1/done"
    1170   var g/eax: grapheme <- read-from-gap-buffer gap
    1171   var x/ecx: int <- copy g
    1172   check-ints-equal x, 0x61/a, "F - test-read-from-gap-buffer/right-1"
    1173   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1174   check-not done?, "F - test-read-from-gap-buffer/right-2/done"
    1175   var g/eax: grapheme <- read-from-gap-buffer gap
    1176   var x/ecx: int <- copy g
    1177   check-ints-equal x, 0x62/b, "F - test-read-from-gap-buffer/right-2"
    1178   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1179   check-not done?, "F - test-read-from-gap-buffer/right-3/done"
    1180   var g/eax: grapheme <- read-from-gap-buffer gap
    1181   var x/ecx: int <- copy g
    1182   check-ints-equal x, 0x63/c, "F - test-read-from-gap-buffer/right-3"
    1183   var done?/eax: boolean <- gap-buffer-scan-done? gap
    1184   check done?, "F - test-read-from-gap-buffer/right-4/done"
    1185   var g/eax: grapheme <- read-from-gap-buffer gap
    1186   var x/ecx: int <- copy g
    1187   check-ints-equal x, 0/nul, "F - test-read-from-gap-buffer/right-4"
    1188 }
    1189
    1190 fn skip-spaces-from-gap-buffer self: (addr gap-buffer) {
    1191   var done?/eax: boolean <- gap-buffer-scan-done? self
    1192   compare done?, 0/false
    1193   break-if-!=
    1194   var g/eax: grapheme <- peek-from-gap-buffer self
    1195   {
    1196     compare g, 0x20/space
    1197     break-if-=
    1198     return
    1199   }
    1200   g <- read-from-gap-buffer self
    1201   loop
    1202 }
    1203
    1204 fn edit-gap-buffer self: (addr gap-buffer), key: grapheme {
    1205   var g/edx: grapheme <- copy key
    1206   {
    1207     compare g, 8/backspace
    1208     break-if-!=
    1209     delete-before-gap self
    1210     return
    1211   }
    1212   {
    1213     compare g, 0x80/left-arrow
    1214     break-if-!=
    1215     var dummy/eax: grapheme <- gap-left self
    1216     return
    1217   }
    1218   {
    1219     compare g, 0x83/right-arrow
    1220     break-if-!=
    1221     var dummy/eax: grapheme <- gap-right self
    1222     return
    1223   }
    1224   {
    1225     compare g, 6/ctrl-f
    1226     break-if-!=
    1227     gap-to-start-of-next-word self
    1228     return
    1229   }
    1230   {
    1231     compare g, 2/ctrl-b
    1232     break-if-!=
    1233     gap-to-end-of-previous-word self
    1234     return
    1235   }
    1236   {
    1237     compare g, 1/ctrl-a
    1238     break-if-!=
    1239     gap-to-previous-start-of-line self
    1240     return
    1241   }
    1242   {
    1243     compare g, 5/ctrl-e
    1244     break-if-!=
    1245     gap-to-next-end-of-line self
    1246     return
    1247   }
    1248   {
    1249     compare g, 0x81/down-arrow
    1250     break-if-!=
    1251     gap-down self
    1252     return
    1253   }
    1254   {
    1255     compare g, 0x82/up-arrow
    1256     break-if-!=
    1257     gap-up self
    1258     return
    1259   }
    1260   {
    1261     compare g, 0x15/ctrl-u
    1262     break-if-!=
    1263     clear-gap-buffer self
    1264     return
    1265   }
    1266   {
    1267     compare g, 9/tab
    1268     break-if-!=
    1269     # tab = 2 spaces
    1270     add-code-point-at-gap self, 0x20/space
    1271     add-code-point-at-gap self, 0x20/space
    1272     return
    1273   }
    1274   # default: insert character
    1275   add-grapheme-at-gap self, g
    1276 }
    1277
    1278 fn gap-to-start-of-next-word self: (addr gap-buffer) {
    1279   var curr/eax: grapheme <- copy 0
    1280   # skip to next space
    1281   {
    1282     curr <- gap-right self
    1283     compare curr, -1
    1284     break-if-=
    1285     compare curr, 0x20/space
    1286     break-if-=
    1287     compare curr, 0xa/newline
    1288     break-if-=
    1289     loop
    1290   }
    1291   # skip past spaces
    1292   {
    1293     curr <- gap-right self
    1294     compare curr, -1
    1295     break-if-=
    1296     compare curr, 0x20/space
    1297     loop-if-=
    1298     compare curr, 0xa/space
    1299     loop-if-=
    1300     curr <- gap-left self
    1301     break
    1302   }
    1303 }
    1304
    1305 fn gap-to-end-of-previous-word self: (addr gap-buffer) {
    1306   var curr/eax: grapheme <- copy 0
    1307   # skip to previous space
    1308   {
    1309     curr <- gap-left self
    1310     compare curr, -1
    1311     break-if-=
    1312     compare curr, 0x20/space
    1313     break-if-=
    1314     compare curr, 0xa/newline
    1315     break-if-=
    1316     loop
    1317   }
    1318   # skip past all spaces but one
    1319   {
    1320     curr <- gap-left self
    1321     compare curr, -1
    1322     break-if-=
    1323     compare curr, 0x20/space
    1324     loop-if-=
    1325     compare curr, 0xa/space
    1326     loop-if-=
    1327     curr <- gap-right self
    1328     break
    1329   }
    1330 }
    1331
    1332 fn gap-to-previous-start-of-line self: (addr gap-buffer) {
    1333   # skip past immediate newline
    1334   var dummy/eax: grapheme <- gap-left self
    1335   # skip to previous newline
    1336   {
    1337     dummy <- gap-left self
    1338     {
    1339       compare dummy, -1
    1340       break-if-!=
    1341       return
    1342     }
    1343     {
    1344       compare dummy, 0xa/newline
    1345       break-if-!=
    1346       dummy <- gap-right self
    1347       return
    1348     }
    1349     loop
    1350   }
    1351 }
    1352
    1353 fn gap-to-next-end-of-line self: (addr gap-buffer) {
    1354   # skip past immediate newline
    1355   var dummy/eax: grapheme <- gap-right self
    1356   # skip to next newline
    1357   {
    1358     dummy <- gap-right self
    1359     {
    1360       compare dummy, -1
    1361       break-if-!=
    1362       return
    1363     }
    1364     {
    1365       compare dummy, 0xa/newline
    1366       break-if-!=
    1367       dummy <- gap-left self
    1368       return
    1369     }
    1370     loop
    1371   }
    1372 }
    1373
    1374 fn gap-up self: (addr gap-buffer) {
    1375   # compute column
    1376   var col/edx: int <- count-columns-to-start-of-line self
    1377   #
    1378   gap-to-previous-start-of-line self
    1379   # skip ahead by up to col on previous line
    1380   var i/ecx: int <- copy 0
    1381   {
    1382     compare i, col
    1383     break-if->=
    1384     var curr/eax: grapheme <- gap-right self
    1385     {
    1386       compare curr, -1
    1387       break-if-!=
    1388       return
    1389     }
    1390     compare curr, 0xa/newline
    1391     {
    1392       break-if-!=
    1393       curr <- gap-left self
    1394       return
    1395     }
    1396     i <- increment
    1397     loop
    1398   }
    1399 }
    1400
    1401 fn gap-down self: (addr gap-buffer) {
    1402   # compute column
    1403   var col/edx: int <- count-columns-to-start-of-line self
    1404   # skip to start of next line
    1405   gap-to-end-of-line self
    1406   var dummy/eax: grapheme <- gap-right self
    1407   # skip ahead by up to col on previous line
    1408   var i/ecx: int <- copy 0
    1409   {
    1410     compare i, col
    1411     break-if->=
    1412     var curr/eax: grapheme <- gap-right self
    1413     {
    1414       compare curr, -1
    1415       break-if-!=
    1416       return
    1417     }
    1418     compare curr, 0xa/newline
    1419     {
    1420       break-if-!=
    1421       curr <- gap-left self
    1422       return
    1423     }
    1424     i <- increment
    1425     loop
    1426   }
    1427 }
    1428
    1429 fn count-columns-to-start-of-line self: (addr gap-buffer) -> _/edx: int {
    1430   var count/edx: int <- copy 0
    1431   var dummy/eax: grapheme <- copy 0
    1432   # skip to previous newline
    1433   {
    1434     dummy <- gap-left self
    1435     {
    1436       compare dummy, -1
    1437       break-if-!=
    1438       return count
    1439     }
    1440     {
    1441       compare dummy, 0xa/newline
    1442       break-if-!=
    1443       dummy <- gap-right self
    1444       return count
    1445     }
    1446     count <- increment
    1447     loop
    1448   }
    1449   return count
    1450 }
    1451
    1452 fn gap-to-end-of-line self: (addr gap-buffer) {
    1453   var dummy/eax: grapheme <- copy 0
    1454   # skip to next newline
    1455   {
    1456     dummy <- gap-right self
    1457     {
    1458       compare dummy, -1
    1459       break-if-!=
    1460       return
    1461     }
    1462     {
    1463       compare dummy, 0xa/newline
    1464       break-if-!=
    1465       dummy <- gap-left self
    1466       return
    1467     }
    1468     loop
    1469   }
    1470 }