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