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