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