https://github.com/akkartik/mu/blob/main/baremetal/shell/gap-buffer.mu
  1 # primitive for editing a single word
  2 # parameter: max-word-size = 16 graphemes
  3 
  4 type gap-buffer {
  5   left: grapheme-stack
  6   right: grapheme-stack
  7 }
  8 
  9 fn initialize-gap-buffer _self: (addr gap-buffer) {
 10   var self/esi: (addr gap-buffer) <- copy _self
 11   var left/eax: (addr grapheme-stack) <- get self, left
 12   initialize-grapheme-stack left, 0x10/max-word-size
 13   var right/eax: (addr grapheme-stack) <- get self, right
 14   initialize-grapheme-stack right, 0x10/max-word-size
 15 }
 16 
 17 # just for tests
 18 fn initialize-gap-buffer-with self: (addr gap-buffer), s: (addr array byte) {
 19   initialize-gap-buffer self
 20   var stream-storage: (stream byte 0x10/max-word-size)
 21   var stream/ecx: (addr stream byte) <- address stream-storage
 22   write stream, s
 23   {
 24     var done?/eax: boolean <- stream-empty? stream
 25     compare done?, 0/false
 26     break-if-!=
 27     var g/eax: grapheme <- read-grapheme stream
 28     add-grapheme-at-gap self, g
 29     loop
 30   }
 31 }
 32 
 33 fn emit-gap-buffer _self: (addr gap-buffer), out: (addr stream byte) {
 34   var self/esi: (addr gap-buffer) <- copy _self
 35   clear-stream out
 36   var left/eax: (addr grapheme-stack) <- get self, left
 37   emit-stack-from-bottom left, out
 38   var right/eax: (addr grapheme-stack) <- get self, right
 39   emit-stack-from-top right, out
 40 }
 41 
 42 # dump stack from bottom to top
 43 fn emit-stack-from-bottom _self: (addr grapheme-stack), out: (addr stream byte) {
 44   var self/esi: (addr grapheme-stack) <- copy _self
 45   var data-ah/edi: (addr handle array grapheme) <- get self, data
 46   var _data/eax: (addr array grapheme) <- lookup *data-ah
 47   var data/edi: (addr array grapheme) <- copy _data
 48   var top-addr/ecx: (addr int) <- get self, top
 49   var i/eax: int <- copy 0
 50   {
 51     compare i, *top-addr
 52     break-if->=
 53     var g/edx: (addr grapheme) <- index data, i
 54     write-grapheme out, *g
 55     i <- increment
 56     loop
 57   }
 58 }
 59 
 60 # dump stack from top to bottom
 61 fn emit-stack-from-top _self: (addr grapheme-stack), out: (addr stream byte) {
 62   var self/esi: (addr grapheme-stack) <- copy _self
 63   var data-ah/edi: (addr handle array grapheme) <- get self, data
 64   var _data/eax: (addr array grapheme) <- lookup *data-ah
 65   var data/edi: (addr array grapheme) <- copy _data
 66   var top-addr/ecx: (addr int) <- get self, top
 67   var i/eax: int <- copy *top-addr
 68   i <- decrement
 69   {
 70     compare i, 0
 71     break-if-<
 72     var g/edx: (addr grapheme) <- index data, i
 73     write-grapheme out, *g
 74     i <- decrement
 75     loop
 76   }
 77 }
 78 
 79 # We implicitly render everything editable in a single color, and assume the
 80 # cursor is a single other color.
 81 fn render-gap-buffer screen: (addr screen), _gap: (addr gap-buffer), x: int, y: int, render-cursor?: boolean -> _/eax: int {
 82   var gap/esi: (addr gap-buffer) <- copy _gap
 83   var left/eax: (addr grapheme-stack) <- get gap, left
 84   var x2/eax: int <- render-stack-from-bottom screen, left, x, y
 85   var right/ecx: (addr grapheme-stack) <- get gap, right
 86   x2 <- render-stack-from-top screen, right, x2, y, render-cursor?
 87   var x3/ebx: int <- copy x2
 88   # decide whether we still need to print a cursor
 89   var bg/edx: int <- copy 0
 90   compare render-cursor?, 0/false
 91   {
 92     break-if-=
 93     # if the right side is empty, grapheme stack didn't print the cursor
 94     var empty?/eax: boolean <- grapheme-stack-empty? right
 95     compare empty?, 0/false
 96     break-if-=
 97     bg <- copy 7/cursor
 98   }
 99   # print a grapheme either way so that cursor position doesn't affect printed width
100   var space/eax: grapheme <- copy 0x20
101   draw-grapheme screen, space, x3, y, 3/fg=cyan, bg
102   x3 <- increment
103   return x3
104 }
105 
106 fn gap-buffer-length _gap: (addr gap-buffer) -> _/eax: int {
107   var gap/esi: (addr gap-buffer) <- copy _gap
108   var left/eax: (addr grapheme-stack) <- get gap, left
109   var tmp/eax: (addr int) <- get left, top
110   var left-length/ecx: int <- copy *tmp
111   var right/esi: (addr grapheme-stack) <- get gap, right
112   tmp <- get right, top
113   var result/eax: int <- copy *tmp
114   result <- add left-length
115   return result
116 }
117 
118 fn add-grapheme-at-gap _self: (addr gap-buffer), g: grapheme {
119   var self/esi: (addr gap-buffer) <- copy _self
120   var left/eax: (addr grapheme-stack) <- get self, left
121   push-grapheme-stack left, g
122 }
123 
124 fn gap-to-start self: (addr gap-buffer) {
125   {
126     var curr/eax: grapheme <- gap-left self
127     compare curr, -1
128     loop-if-!=
129   }
130 }
131 
132 fn gap-to-end self: (addr gap-buffer) {
133   {
134     var curr/eax: grapheme <- gap-right self
135     compare curr, -1
136     loop-if-!=
137   }
138 }
139 
140 fn gap-at-start? _self: (addr gap-buffer) -> _/eax: boolean {
141   var self/esi: (addr gap-buffer) <- copy _self
142   var left/eax: (addr grapheme-stack) <- get self, left
143   var result/eax: boolean <- grapheme-stack-empty? left
144   return result
145 }
146 
147 fn gap-at-end? _self: (addr gap-buffer) -> _/eax: boolean {
148   var self/esi: (addr gap-buffer) <- copy _self
149   var right/eax: (addr grapheme-stack) <- get self, right
150   var result/eax: boolean <- grapheme-stack-empty? right
151   return result
152 }
153 
154 fn gap-right _self: (addr gap-buffer) -> _/eax: grapheme {
155   var self/esi: (addr gap-buffer) <- copy _self
156   var g/eax: grapheme <- copy 0
157   var right/ecx: (addr grapheme-stack) <- get self, right
158   g <- pop-grapheme-stack right
159   compare g, -1
160   {
161     break-if-=
162     var left/ecx: (addr grapheme-stack) <- get self, left
163     push-grapheme-stack left, g
164   }
165   return g
166 }
167 
168 fn gap-left _self: (addr gap-buffer) -> _/eax: grapheme {
169   var self/esi: (addr gap-buffer) <- copy _self
170   var g/eax: grapheme <- copy 0
171   {
172     var left/ecx: (addr grapheme-stack) <- get self, left
173     g <- pop-grapheme-stack left
174   }
175   compare g, -1
176   {
177     break-if-=
178     var right/ecx: (addr grapheme-stack) <- get self, right
179     push-grapheme-stack right, g
180   }
181   return g
182 }
183 
184 fn index-of-gap _self: (addr gap-buffer) -> _/eax: int {
185   var self/eax: (addr gap-buffer) <- copy _self
186   var left/eax: (addr grapheme-stack) <- get self, left
187   var top-addr/eax: (addr int) <- get left, top
188   var result/eax: int <- copy *top-addr
189   return result
190 }
191 
192 fn first-grapheme-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
193   var self/esi: (addr gap-buffer) <- copy _self
194   # try to read from left
195   var left/eax: (addr grapheme-stack) <- get self, left
196   var top-addr/ecx: (addr int) <- get left, top
197   compare *top-addr, 0
198   {
199     break-if-<=
200     var data-ah/eax: (addr handle array grapheme) <- get left, data
201     var data/eax: (addr array grapheme) <- lookup *data-ah
202     var result-addr/eax: (addr grapheme) <- index data, 0
203     return *result-addr
204   }
205   # try to read from right
206   var right/eax: (addr grapheme-stack) <- get self, right
207   top-addr <- get right, top
208   compare *top-addr, 0
209   {
210     break-if-<=
211     var data-ah/eax: (addr handle array grapheme) <- get right, data
212     var data/eax: (addr array grapheme) <- lookup *data-ah
213     var top/ecx: int <- copy *top-addr
214     top <- decrement
215     var result-addr/eax: (addr grapheme) <- index data, top
216     return *result-addr
217   }
218   # give up
219   return -1
220 }
221 
222 fn grapheme-before-cursor-in-gap-buffer _self: (addr gap-buffer) -> _/eax: grapheme {
223   var self/esi: (addr gap-buffer) <- copy _self
224   # try to read from left
225   var left/ecx: (addr grapheme-stack) <- get self, left
226   var top-addr/edx: (addr int) <- get left, top
227   compare *top-addr, 0
228   {
229     break-if-<=
230     var result/eax: grapheme <- pop-grapheme-stack left
231     push-grapheme-stack left, result
232     return result
233   }
234   # give up
235   return -1
236 }
237 
238 fn delete-before-gap _self: (addr gap-buffer) {
239   var self/eax: (addr gap-buffer) <- copy _self
240   var left/eax: (addr grapheme-stack) <- get self, left
241   var dummy/eax: grapheme <- pop-grapheme-stack left
242 }
243 
244 fn pop-after-gap _self: (addr gap-buffer) -> _/eax: grapheme {
245   var self/eax: (addr gap-buffer) <- copy _self
246   var right/eax: (addr grapheme-stack) <- get self, right
247   var result/eax: grapheme <- pop-grapheme-stack right
248   return result
249 }
250 
251 fn gap-buffer-equal? _self: (addr gap-buffer), s: (addr array byte) -> _/eax: boolean {
252   var self/esi: (addr gap-buffer) <- copy _self
253   # complication: graphemes may be multiple bytes
254   # so don't rely on length
255   # instead turn the expected result into a stream and arrange to read from it in order
256   var stream-storage: (stream byte 0x10/max-word-size)
257   var expected-stream/ecx: (addr stream byte) <- address stream-storage
258   write expected-stream, s
259   # compare left
260   var left/edx: (addr grapheme-stack) <- get self, left
261   var result/eax: boolean <- prefix-match? left, expected-stream
262   compare result, 0/false
263   {
264     break-if-!=
265     return result
266   }
267   # compare right
268   var right/edx: (addr grapheme-stack) <- get self, right
269   result <- suffix-match? right, expected-stream
270   compare result, 0/false
271   {
272     break-if-!=
273     return result
274   }
275   # ensure there's nothing left over
276   result <- stream-empty? expected-stream
277   return result
278 }
279 
280 fn test-gap-buffer-equal-from-end {
281   var _g: gap-buffer
282   var g/esi: (addr gap-buffer) <- address _g
283   initialize-gap-buffer g
284   #
285   var c/eax: grapheme <- copy 0x61/a
286   add-grapheme-at-gap g, c
287   add-grapheme-at-gap g, c
288   add-grapheme-at-gap g, c
289   # gap is at end (right is empty)
290   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
291   check result, "F - test-gap-buffer-equal-from-end"
292 }
293 
294 fn test-gap-buffer-equal-from-middle {
295   var _g: gap-buffer
296   var g/esi: (addr gap-buffer) <- address _g
297   initialize-gap-buffer g
298   #
299   var c/eax: grapheme <- copy 0x61/a
300   add-grapheme-at-gap g, c
301   add-grapheme-at-gap g, c
302   add-grapheme-at-gap g, c
303   var dummy/eax: grapheme <- gap-left g
304   # gap is in the middle
305   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
306   check result, "F - test-gap-buffer-equal-from-middle"
307 }
308 
309 fn test-gap-buffer-equal-from-start {
310   var _g: gap-buffer
311   var g/esi: (addr gap-buffer) <- address _g
312   initialize-gap-buffer g
313   #
314   var c/eax: grapheme <- copy 0x61/a
315   add-grapheme-at-gap g, c
316   add-grapheme-at-gap g, c
317   add-grapheme-at-gap g, c
318   var dummy/eax: grapheme <- gap-left g
319   dummy <- gap-left g
320   dummy <- gap-left g
321   # gap is at the start
322   var result/eax: boolean <- gap-buffer-equal? g, "aaa"
323   check result, "F - test-gap-buffer-equal-from-start"
324 }
325 
326 fn test-gap-buffer-equal-fails {
327   # g = "aaa"
328   var _g: gap-buffer
329   var g/esi: (addr gap-buffer) <- address _g
330   initialize-gap-buffer g
331   var c/eax: grapheme <- copy 0x61/a
332   add-grapheme-at-gap g, c
333   add-grapheme-at-gap g, c
334   add-grapheme-at-gap g, c
335   #
336   var result/eax: boolean <- gap-buffer-equal? g, "aa"
337   check-not result, "F - test-gap-buffer-equal-fails"
338 }
339 
340 fn gap-buffers-equal? self: (addr gap-buffer), g: (addr gap-buffer) -> _/eax: boolean {
341   var tmp/eax: int <- gap-buffer-length self
342   var len/ecx: int <- copy tmp
343   var leng/eax: int <- gap-buffer-length g
344   compare len, leng
345   {
346     break-if-=
347     return 0/false
348   }
349   var i/edx: int <- copy 0
350   {
351     compare i, len
352     break-if->=
353     {
354       var tmp/eax: grapheme <- gap-index self, i
355       var curr/ecx: grapheme <- copy tmp
356       var currg/eax: grapheme <- gap-index g, i
357       compare curr, currg
358       break-if-=
359       return 0/false
360     }
361     i <- increment
362     loop
363   }
364   return 1/true
365 }
366 
367 fn gap-index _self: (addr gap-buffer), _n: int -> _/eax: grapheme {
368   var self/esi: (addr gap-buffer) <- copy _self
369   var n/ebx: int <- copy _n
370   # if n < left->length, index into left
371   var left/edi: (addr grapheme-stack) <- get self, left
372   var left-len-a/edx: (addr int) <- get left, top
373   compare n, *left-len-a
374   {
375     break-if->=
376     var data-ah/eax: (addr handle array grapheme) <- get left, data
377     var data/eax: (addr array grapheme) <- lookup *data-ah
378     var result/eax: (addr grapheme) <- index data, n
379     return *result
380   }
381   # shrink n
382   n <- subtract *left-len-a
383   # if n < right->length, index into right
384   var right/edi: (addr grapheme-stack) <- get self, right
385   var right-len-a/edx: (addr int) <- get right, top
386   compare n, *right-len-a
387   {
388     break-if->=
389     var data-ah/eax: (addr handle array grapheme) <- get right, data
390     var data/eax: (addr array grapheme) <- lookup *data-ah
391     var result/eax: (addr grapheme) <- index data, n
392     return *result
393   }
394   # error
395   abort "gap-index: out of bounds"
396   return 0
397 }
398 
399 fn test-gap-buffers-equal? {
400   var _a: gap-buffer
401   var a/esi: (addr gap-buffer) <- address _a
402   initialize-gap-buffer-with a, "abc"
403   var _b: gap-buffer
404   var b/edi: (addr gap-buffer) <- address _b
405   initialize-gap-buffer-with b, "abc"
406   var _c: gap-buffer
407   var c/ebx: (addr gap-buffer) <- address _c
408   initialize-gap-buffer-with c, "ab"
409   var _d: gap-buffer
410   var d/edx: (addr gap-buffer) <- address _d
411   initialize-gap-buffer-with d, "abd"
412   #
413   var result/eax: boolean <- gap-buffers-equal? a, a
414   check result, "F - test-gap-buffers-equal? - reflexive"
415   result <- gap-buffers-equal? a, b
416   check result, "F - test-gap-buffers-equal? - equal"
417   # length not equal
418   result <- gap-buffers-equal? a, c
419   check-not result, "F - test-gap-buffers-equal? - not equal"
420   # contents not equal
421   result <- gap-buffers-equal? a, d
422   check-not result, "F - test-gap-buffers-equal? - not equal 2"
423   result <- gap-buffers-equal? d, a
424   check-not result, "F - test-gap-buffers-equal? - not equal 3"
425 }
426 
427 fn copy-gap-buffer _src-ah: (addr handle gap-buffer), _dest-ah: (addr handle gap-buffer) {
428   # obtain src-a, dest-a
429   var src-ah/eax: (addr handle gap-buffer) <- copy _src-ah
430   var _src-a/eax: (addr gap-buffer) <- lookup *src-ah
431   var src-a/esi: (addr gap-buffer) <- copy _src-a
432   var dest-ah/eax: (addr handle gap-buffer) <- copy _dest-ah
433   var _dest-a/eax: (addr gap-buffer) <- lookup *dest-ah
434   var dest-a/edi: (addr gap-buffer) <- copy _dest-a
435   # copy left grapheme-stack
436   var src/ecx: (addr grapheme-stack) <- get src-a, left
437   var dest/edx: (addr grapheme-stack) <- get dest-a, left
438   copy-grapheme-stack src, dest
439   # copy right grapheme-stack
440   src <- get src-a, right
441   dest <- get dest-a, right
442   copy-grapheme-stack src, dest
443 }
444 
445 fn gap-buffer-is-decimal-integer? _self: (addr gap-buffer) -> _/eax: boolean {
446   var self/esi: (addr gap-buffer) <- copy _self
447   var curr/ecx: (addr grapheme-stack) <- get self, left
448   var result/eax: boolean <- grapheme-stack-is-decimal-integer? curr
449   {
450     compare result, 0/false
451     break-if-=
452     curr <- get self, right
453     result <- grapheme-stack-is-decimal-integer? curr
454   }
455   return result
456 }
457 
458 fn test-render-gap-buffer-without-cursor {
459   # setup
460   var gap-storage: gap-buffer
461   var gap/esi: (addr gap-buffer) <- address gap-storage
462   initialize-gap-buffer-with gap, "abc"
463   # setup: screen
464   var screen-on-stack: screen
465   var screen/edi: (addr screen) <- address screen-on-stack
466   initialize-screen screen, 5, 4
467   #
468   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 0/no-cursor
469   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-without-cursor"
470   check-ints-equal x, 4, "F - test-render-gap-buffer-without-cursor: result"
471                                                                 # abc
472   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-gap-buffer-without-cursor: bg"
473 }
474 
475 fn test-render-gap-buffer-with-cursor-at-end {
476   # setup
477   var gap-storage: gap-buffer
478   var gap/esi: (addr gap-buffer) <- address gap-storage
479   initialize-gap-buffer-with gap, "abc"
480   gap-to-end gap
481   # setup: screen
482   var screen-on-stack: screen
483   var screen/edi: (addr screen) <- address screen-on-stack
484   initialize-screen screen, 5, 4
485   #
486   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
487   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-end"
488   # we've drawn one extra grapheme for the cursor
489   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-end: result"
490                                                                 # abc
491   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   |", "F - test-render-gap-buffer-with-cursor-at-end: bg"
492 }
493 
494 fn test-render-gap-buffer-with-cursor-in-middle {
495   # setup
496   var gap-storage: gap-buffer
497   var gap/esi: (addr gap-buffer) <- address gap-storage
498   initialize-gap-buffer-with gap, "abc"
499   gap-to-end gap
500   var dummy/eax: grapheme <- gap-left gap
501   # setup: screen
502   var screen-on-stack: screen
503   var screen/edi: (addr screen) <- address screen-on-stack
504   initialize-screen screen, 5, 4
505   #
506   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
507   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-in-middle"
508   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-in-middle: result"
509                                                                 # abc
510   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "  | ", "F - test-render-gap-buffer-with-cursor-in-middle: bg"
511 }
512 
513 fn test-render-gap-buffer-with-cursor-at-start {
514   var gap-storage: gap-buffer
515   var gap/esi: (addr gap-buffer) <- address gap-storage
516   initialize-gap-buffer-with gap, "abc"
517   gap-to-start gap
518   # setup: screen
519   var screen-on-stack: screen
520   var screen/edi: (addr screen) <- address screen-on-stack
521   initialize-screen screen, 5, 4
522   #
523   var x/eax: int <- render-gap-buffer screen, gap, 0/x, 0/y, 1/show-cursor
524   check-screen-row screen, 0/y, "abc ", "F - test-render-gap-buffer-with-cursor-at-start"
525   check-ints-equal x, 4, "F - test-render-gap-buffer-with-cursor-at-start: result"
526                                                                 # abc
527   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|   ", "F - test-render-gap-buffer-with-cursor-at-start: bg"
528 }