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