https://github.com/akkartik/mu/blob/main/shell/grapheme-stack.mu
  1 # grapheme stacks are the smallest unit of editable text
  2 
  3 type grapheme-stack {
  4   data: (handle array grapheme)
  5   top: int
  6 }
  7 
  8 fn initialize-grapheme-stack _self: (addr grapheme-stack), n: int {
  9   var self/esi: (addr grapheme-stack) <- copy _self
 10   var d/edi: (addr handle array grapheme) <- get self, data
 11   populate d, n
 12   var top/eax: (addr int) <- get self, top
 13   copy-to *top, 0
 14 }
 15 
 16 fn clear-grapheme-stack _self: (addr grapheme-stack) {
 17   var self/esi: (addr grapheme-stack) <- copy _self
 18   var top/eax: (addr int) <- get self, top
 19   copy-to *top, 0
 20 }
 21 
 22 fn grapheme-stack-empty? _self: (addr grapheme-stack) -> _/eax: boolean {
 23   var self/esi: (addr grapheme-stack) <- copy _self
 24   var top/eax: (addr int) <- get self, top
 25   compare *top, 0
 26   {
 27     break-if-!=
 28     return 1/true
 29   }
 30   return 0/false
 31 }
 32 
 33 fn grapheme-stack-length _self: (addr grapheme-stack) -> _/eax: int {
 34   var self/esi: (addr grapheme-stack) <- copy _self
 35   var top/eax: (addr int) <- get self, top
 36   return *top
 37 }
 38 
 39 fn push-grapheme-stack _self: (addr grapheme-stack), _val: grapheme {
 40   var self/esi: (addr grapheme-stack) <- copy _self
 41   var top-addr/ecx: (addr int) <- get self, top
 42   var data-ah/edx: (addr handle array grapheme) <- get self, data
 43   var data/eax: (addr array grapheme) <- lookup *data-ah
 44   var top/edx: int <- copy *top-addr
 45   var dest-addr/edx: (addr grapheme) <- index data, top
 46   var val/eax: grapheme <- copy _val
 47   copy-to *dest-addr, val
 48   add-to *top-addr, 1
 49 }
 50 
 51 fn pop-grapheme-stack _self: (addr grapheme-stack) -> _/eax: grapheme {
 52   var self/esi: (addr grapheme-stack) <- copy _self
 53   var top-addr/ecx: (addr int) <- get self, top
 54   {
 55     compare *top-addr, 0
 56     break-if->
 57     return -1
 58   }
 59   subtract-from *top-addr, 1
 60   var data-ah/edx: (addr handle array grapheme) <- get self, data
 61   var data/eax: (addr array grapheme) <- lookup *data-ah
 62   var top/edx: int <- copy *top-addr
 63   var result-addr/eax: (addr grapheme) <- index data, top
 64   return *result-addr
 65 }
 66 
 67 fn copy-grapheme-stack _src: (addr grapheme-stack), dest: (addr grapheme-stack) {
 68   var src/esi: (addr grapheme-stack) <- copy _src
 69   var data-ah/edi: (addr handle array grapheme) <- get src, data
 70   var _data/eax: (addr array grapheme) <- lookup *data-ah
 71   var data/edi: (addr array grapheme) <- copy _data
 72   var top-addr/ecx: (addr int) <- get src, top
 73   var i/eax: int <- copy 0
 74   {
 75     compare i, *top-addr
 76     break-if->=
 77     var g/edx: (addr grapheme) <- index data, i
 78     push-grapheme-stack dest, *g
 79     i <- increment
 80     loop
 81   }
 82 }
 83 
 84 # dump stack to screen from bottom to top
 85 # colors hardcoded
 86 fn render-stack-from-bottom-wrapping-right-then-down screen: (addr screen), _self: (addr grapheme-stack), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, highlight-matching-open-paren?: boolean, open-paren-depth: int -> _/eax: int, _/ecx: int {
 87   var self/esi: (addr grapheme-stack) <- copy _self
 88   var matching-open-paren-index/edx: int <- get-matching-open-paren-index self, highlight-matching-open-paren?, open-paren-depth
 89   var data-ah/edi: (addr handle array grapheme) <- get self, data
 90   var _data/eax: (addr array grapheme) <- lookup *data-ah
 91   var data/edi: (addr array grapheme) <- copy _data
 92   var x/eax: int <- copy _x
 93   var y/ecx: int <- copy _y
 94   var top-addr/esi: (addr int) <- get self, top
 95   var i/ebx: int <- copy 0
 96   {
 97     compare i, *top-addr
 98     break-if->=
 99     {
100       var g/esi: (addr grapheme) <- index data, i
101       var fg: int
102       copy-to fg, 3/cyan
103       {
104         compare i, matching-open-paren-index
105         break-if-!=
106         copy-to fg, 0xf/highlight
107       }
108       x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, fg, 0/bg
109     }
110     i <- increment
111     loop
112   }
113   return x, y
114 }
115 
116 # helper for small words
117 fn render-stack-from-bottom screen: (addr screen), self: (addr grapheme-stack), x: int, y: int, highlight-matching-open-paren?: boolean, open-paren-depth: int -> _/eax: int {
118   var _width/eax: int <- copy 0
119   var _height/ecx: int <- copy 0
120   _width, _height <- screen-size screen
121   var width/edx: int <- copy _width
122   var height/ebx: int <- copy _height
123   var x2/eax: int <- copy 0
124   var y2/ecx: int <- copy 0
125   x2, y2 <- render-stack-from-bottom-wrapping-right-then-down screen, self, x, y, width, height, x, y, highlight-matching-open-paren?, open-paren-depth
126   return x2  # y2? yolo
127 }
128 
129 # dump stack to screen from top to bottom
130 # optionally render a 'cursor' with the top grapheme
131 fn render-stack-from-top-wrapping-right-then-down screen: (addr screen), _self: (addr grapheme-stack), xmin: int, ymin: int, xmax: int, ymax: int, _x: int, _y: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
132   var self/esi: (addr grapheme-stack) <- copy _self
133   var matching-close-paren-index/edx: int <- get-matching-close-paren-index self, render-cursor?
134   var data-ah/eax: (addr handle array grapheme) <- get self, data
135   var _data/eax: (addr array grapheme) <- lookup *data-ah
136   var data/edi: (addr array grapheme) <- copy _data
137   var x/eax: int <- copy _x
138   var y/ecx: int <- copy _y
139   var top-addr/ebx: (addr int) <- get self, top
140   var i/ebx: int <- copy *top-addr
141   i <- decrement
142   # if render-cursor?, peel off first iteration
143   {
144     compare render-cursor?, 0/false
145     break-if-=
146     compare i, 0
147     break-if-<
148     var g/esi: (addr grapheme) <- index data, i
149     x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, 3/fg=cyan, 7/bg=cursor
150     i <- decrement
151   }
152   # remaining iterations
153   {
154     compare i, 0
155     break-if-<
156     # highlight matching paren if needed
157     var fg: int
158     copy-to fg, 3/cyan
159     compare i, matching-close-paren-index
160     {
161       break-if-!=
162       copy-to fg, 0xf/highlight
163     }
164     #
165     var g/esi: (addr grapheme) <- index data, i
166     x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, fg, 0/bg=cursor
167     i <- decrement
168     loop
169   }
170   return x, y
171 }
172 
173 # helper for small words
174 fn render-stack-from-top screen: (addr screen), self: (addr grapheme-stack), x: int, y: int, render-cursor?: boolean -> _/eax: int {
175   var _width/eax: int <- copy 0
176   var _height/ecx: int <- copy 0
177   _width, _height <- screen-size screen
178   var width/edx: int <- copy _width
179   var height/ebx: int <- copy _height
180   var x2/eax: int <- copy 0
181   var y2/ecx: int <- copy 0
182   x2, y2 <- render-stack-from-top-wrapping-right-then-down screen, self, x, y, width, height, x, y, render-cursor?
183   return x2  # y2? yolo
184 }
185 
186 fn test-render-grapheme-stack {
187   # setup: gs = "abc"
188   var gs-storage: grapheme-stack
189   var gs/edi: (addr grapheme-stack) <- address gs-storage
190   initialize-grapheme-stack gs, 5
191   var g/eax: grapheme <- copy 0x61/a
192   push-grapheme-stack gs, g
193   g <- copy 0x62/b
194   push-grapheme-stack gs, g
195   g <- copy 0x63/c
196   push-grapheme-stack gs, g
197   # setup: screen
198   var screen-on-stack: screen
199   var screen/esi: (addr screen) <- address screen-on-stack
200   initialize-screen screen, 5, 4
201   #
202   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 0/y, 0/no-highlight-matching-open-paren, 0/open-paren-depth
203   check-screen-row screen, 0/y, "abc ", "F - test-render-grapheme-stack from bottom"
204   check-ints-equal x, 3, "F - test-render-grapheme-stack from bottom: result"
205   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   ", "F - test-render-grapheme-stack from bottom: bg"
206   #
207   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 1/y, 0/cursor=false
208   check-screen-row screen, 1/y, "cba ", "F - test-render-grapheme-stack from top without cursor"
209   check-ints-equal x, 3, "F - test-render-grapheme-stack from top without cursor: result"
210   check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "   ", "F - test-render-grapheme-stack from top without cursor: bg"
211   #
212   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 2/y, 1/cursor=true
213   check-screen-row screen, 2/y, "cba ", "F - test-render-grapheme-stack from top with cursor"
214   check-ints-equal x, 3, "F - test-render-grapheme-stack from top with cursor: result"
215   check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "|   ", "F - test-render-grapheme-stack from top with cursor: bg"
216 }
217 
218 fn test-render-grapheme-stack-while-highlighting-matching-close-paren {
219   # setup: gs = "(b)"
220   var gs-storage: grapheme-stack
221   var gs/edi: (addr grapheme-stack) <- address gs-storage
222   initialize-grapheme-stack gs, 5
223   var g/eax: grapheme <- copy 0x29/close-paren
224   push-grapheme-stack gs, g
225   g <- copy 0x62/b
226   push-grapheme-stack gs, g
227   g <- copy 0x28/open-paren
228   push-grapheme-stack gs, g
229   # setup: screen
230   var screen-on-stack: screen
231   var screen/esi: (addr screen) <- address screen-on-stack
232   initialize-screen screen, 5, 4
233   #
234   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 2/y, 1/cursor=true
235   check-screen-row                      screen,               2/y, "(b) ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren"
236   check-background-color-in-screen-row  screen, 7/bg=cursor,  2/y, "|   ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren: cursor"
237   check-screen-row-in-color             screen, 0xf/fg=white, 2/y, "  ) ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren: matching paren"
238 }
239 
240 fn test-render-grapheme-stack-while-highlighting-matching-close-paren-2 {
241   # setup: gs = "(a (b)) c"
242   var gs-storage: grapheme-stack
243   var gs/edi: (addr grapheme-stack) <- address gs-storage
244   initialize-grapheme-stack gs, 0x10
245   var g/eax: grapheme <- copy 0x63/c
246   push-grapheme-stack gs, g
247   g <- copy 0x20/space
248   push-grapheme-stack gs, g
249   g <- copy 0x29/close-paren
250   push-grapheme-stack gs, g
251   g <- copy 0x29/close-paren
252   push-grapheme-stack gs, g
253   g <- copy 0x62/b
254   push-grapheme-stack gs, g
255   g <- copy 0x28/open-paren
256   push-grapheme-stack gs, g
257   g <- copy 0x20/space
258   push-grapheme-stack gs, g
259   g <- copy 0x61/a
260   push-grapheme-stack gs, g
261   g <- copy 0x28/open-paren
262   push-grapheme-stack gs, g
263   # setup: screen
264   var screen-on-stack: screen
265   var screen/esi: (addr screen) <- address screen-on-stack
266   initialize-screen screen, 5, 4
267   #
268   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 2/y, 1/cursor=true
269   check-screen-row                      screen,               2/y, "(a (b)) c ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren-2"
270   check-background-color-in-screen-row  screen, 7/bg=cursor,  2/y, "|         ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren-2: cursor"
271   check-screen-row-in-color             screen, 0xf/fg=white, 2/y, "      )   ", "F - test-render-grapheme-stack-while-highlighting-matching-close-paren-2: matching paren"
272 }
273 
274 fn test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end {
275   # setup: gs = "(b)"
276   var gs-storage: grapheme-stack
277   var gs/edi: (addr grapheme-stack) <- address gs-storage
278   initialize-grapheme-stack gs, 5
279   var g/eax: grapheme <- copy 0x28/open-paren
280   push-grapheme-stack gs, g
281   g <- copy 0x62/b
282   push-grapheme-stack gs, g
283   g <- copy 0x29/close-paren
284   push-grapheme-stack gs, g
285   # setup: screen
286   var screen-on-stack: screen
287   var screen/esi: (addr screen) <- address screen-on-stack
288   initialize-screen screen, 5, 4
289   #
290   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 2/y, 1/highlight-matching-open-paren, 1/open-paren-depth
291   check-screen-row          screen,               2/y, "(b) ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end"
292   check-screen-row-in-color screen, 0xf/fg=white, 2/y, "(   ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end: matching paren"
293 }
294 
295 fn test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end-2 {
296   # setup: gs = "a((b))"
297   var gs-storage: grapheme-stack
298   var gs/edi: (addr grapheme-stack) <- address gs-storage
299   initialize-grapheme-stack gs, 0x10
300   var g/eax: grapheme <- copy 0x61/a
301   push-grapheme-stack gs, g
302   g <- copy 0x28/open-paren
303   push-grapheme-stack gs, g
304   g <- copy 0x28/open-paren
305   push-grapheme-stack gs, g
306   g <- copy 0x62/b
307   push-grapheme-stack gs, g
308   g <- copy 0x29/close-paren
309   push-grapheme-stack gs, g
310   g <- copy 0x29/close-paren
311   push-grapheme-stack gs, g
312   # setup: screen
313   var screen-on-stack: screen
314   var screen/esi: (addr screen) <- address screen-on-stack
315   initialize-screen screen, 5, 4
316   #
317   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 2/y, 1/highlight-matching-open-paren, 1/open-paren-depth
318   check-screen-row          screen,               2/y, "a((b)) ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end-2"
319   check-screen-row-in-color screen, 0xf/fg=white, 2/y, " (     ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-with-close-paren-at-end-2: matching paren"
320 }
321 
322 fn test-render-grapheme-stack-while-highlighting-matching-open-paren {
323   # setup: gs = "(b"
324   var gs-storage: grapheme-stack
325   var gs/edi: (addr grapheme-stack) <- address gs-storage
326   initialize-grapheme-stack gs, 5
327   var g/eax: grapheme <- copy 0x28/open-paren
328   push-grapheme-stack gs, g
329   g <- copy 0x62/b
330   push-grapheme-stack gs, g
331   # setup: screen
332   var screen-on-stack: screen
333   var screen/esi: (addr screen) <- address screen-on-stack
334   initialize-screen screen, 5, 4
335   #
336   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 2/y, 1/highlight-matching-open-paren, 0/open-paren-depth
337   check-screen-row          screen,               2/y, "(b ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren"
338   check-screen-row-in-color screen, 0xf/fg=white, 2/y, "(  ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren: matching paren"
339 }
340 
341 fn test-render-grapheme-stack-while-highlighting-matching-open-paren-2 {
342   # setup: gs = "a((b)"
343   var gs-storage: grapheme-stack
344   var gs/edi: (addr grapheme-stack) <- address gs-storage
345   initialize-grapheme-stack gs, 0x10
346   var g/eax: grapheme <- copy 0x61/a
347   push-grapheme-stack gs, g
348   g <- copy 0x28/open-paren
349   push-grapheme-stack gs, g
350   g <- copy 0x28/open-paren
351   push-grapheme-stack gs, g
352   g <- copy 0x62/b
353   push-grapheme-stack gs, g
354   g <- copy 0x29/close-paren
355   push-grapheme-stack gs, g
356   # setup: screen
357   var screen-on-stack: screen
358   var screen/esi: (addr screen) <- address screen-on-stack
359   initialize-screen screen, 5, 4
360   #
361   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 2/y, 1/highlight-matching-open-paren, 0/open-paren-depth
362   check-screen-row          screen,               2/y, "a((b) ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-2"
363   check-screen-row-in-color screen, 0xf/fg=white, 2/y, " (    ", "F - test-render-grapheme-stack-while-highlighting-matching-open-paren-2: matching paren"
364 }
365 
366 # return the index of the matching close-paren of the grapheme at cursor (top of stack)
367 # or top index if there's no matching close-paren
368 fn get-matching-close-paren-index _self: (addr grapheme-stack), render-cursor?: boolean -> _/edx: int {
369   var self/esi: (addr grapheme-stack) <- copy _self
370   var top-addr/edx: (addr int) <- get self, top
371   # if not rendering cursor, return
372   compare render-cursor?, 0/false
373   {
374     break-if-!=
375     return *top-addr
376   }
377   var data-ah/eax: (addr handle array grapheme) <- get self, data
378   var data/eax: (addr array grapheme) <- lookup *data-ah
379   var i/ecx: int <- copy *top-addr
380   # if stack is empty, return
381   compare i, 0
382   {
383     break-if->
384     return *top-addr
385   }
386   # if cursor is not '(' return
387   i <- decrement
388   var g/esi: (addr grapheme) <- index data, i
389   compare *g, 0x28/open-paren
390   {
391     break-if-=
392     return *top-addr
393   }
394   # otherwise scan to matching paren
395   var paren-count/ebx: int <- copy 1
396   i <- decrement
397   {
398     compare i, 0
399     break-if-<
400     var g/esi: (addr grapheme) <- index data, i
401     compare *g, 0x28/open-paren
402     {
403       break-if-!=
404       paren-count <- increment
405     }
406     compare *g, 0x29/close-paren
407     {
408       break-if-!=
409       compare paren-count, 1
410       {
411         break-if-!=
412         return i
413       }
414       paren-count <- decrement
415     }
416     i <- decrement
417     loop
418   }
419   return *top-addr
420 }
421 
422 # return the index of the first open-paren at the given depth
423 # or top index if there's no matching close-paren
424 fn get-matching-open-paren-index _self: (addr grapheme-stack), control: boolean, depth: int -> _/edx: int {
425   var self/esi: (addr grapheme-stack) <- copy _self
426   var top-addr/edx: (addr int) <- get self, top
427   # if not rendering cursor, return
428   compare control, 0/false
429   {
430     break-if-!=
431     return *top-addr
432   }
433   var data-ah/eax: (addr handle array grapheme) <- get self, data
434   var data/eax: (addr array grapheme) <- lookup *data-ah
435   var i/ecx: int <- copy *top-addr
436   # if stack is empty, return
437   compare i, 0
438   {
439     break-if->
440     return *top-addr
441   }
442   # scan to matching open paren
443   var paren-count/ebx: int <- copy 0
444   i <- decrement
445   {
446     compare i, 0
447     break-if-<
448     var g/esi: (addr grapheme) <- index data, i
449     compare *g, 0x29/close-paren
450     {
451       break-if-!=
452       paren-count <- increment
453     }
454     compare *g, 0x28/open-paren
455     {
456       break-if-!=
457       compare paren-count, depth
458       {
459         break-if-!=
460         return i
461       }
462       paren-count <- decrement
463     }
464     i <- decrement
465     loop
466   }
467   return *top-addr
468 }
469 
470 # compare from bottom
471 # beware: modifies 'stream', which must be disposed of after a false result
472 fn prefix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
473   var self/esi: (addr grapheme-stack) <- copy _self
474   var data-ah/edi: (addr handle array grapheme) <- get self, data
475   var _data/eax: (addr array grapheme) <- lookup *data-ah
476   var data/edi: (addr array grapheme) <- copy _data
477   var top-addr/ecx: (addr int) <- get self, top
478   var i/ebx: int <- copy 0
479   {
480     compare i, *top-addr
481     break-if->=
482     # if curr != expected, return false
483     {
484       var curr-a/edx: (addr grapheme) <- index data, i
485       var expected/eax: grapheme <- read-grapheme s
486       {
487         compare expected, *curr-a
488         break-if-=
489         return 0/false
490       }
491     }
492     i <- increment
493     loop
494   }
495   return 1   # true
496 }
497 
498 # compare from bottom
499 # beware: modifies 'stream', which must be disposed of after a false result
500 fn suffix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
501   var self/esi: (addr grapheme-stack) <- copy _self
502   var data-ah/edi: (addr handle array grapheme) <- get self, data
503   var _data/eax: (addr array grapheme) <- lookup *data-ah
504   var data/edi: (addr array grapheme) <- copy _data
505   var top-addr/eax: (addr int) <- get self, top
506   var i/ebx: int <- copy *top-addr
507   i <- decrement
508   {
509     compare i, 0
510     break-if-<
511     {
512       var curr-a/edx: (addr grapheme) <- index data, i
513       var expected/eax: grapheme <- read-grapheme s
514       # if curr != expected, return false
515       {
516         compare expected, *curr-a
517         break-if-=
518         return 0/false
519       }
520     }
521     i <- decrement
522     loop
523   }
524   return 1   # true
525 }
526 
527 fn grapheme-stack-is-decimal-integer? _self: (addr grapheme-stack) -> _/eax: boolean {
528   var self/esi: (addr grapheme-stack) <- copy _self
529   var data-ah/eax: (addr handle array grapheme) <- get self, data
530   var _data/eax: (addr array grapheme) <- lookup *data-ah
531   var data/edx: (addr array grapheme) <- copy _data
532   var top-addr/ecx: (addr int) <- get self, top
533   var i/ebx: int <- copy 0
534   var result/eax: boolean <- copy 1/true
535   $grapheme-stack-is-integer?:loop: {
536     compare i, *top-addr
537     break-if->=
538     var g/edx: (addr grapheme) <- index data, i
539     result <- decimal-digit? *g
540     compare result, 0/false
541     break-if-=
542     i <- increment
543     loop
544   }
545   return result
546 }