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 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: code-point <- copy 0x20
 413   x2, y2 <- render-code-point 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-storage: screen
 822   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 840   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 860   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 877   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 894   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 912   var screen/edi: (addr screen) <- address screen-storage
 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-storage: screen
 929   var screen/edi: (addr screen) <- address screen-storage
 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 }