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