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