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