From 8cff1c04e38787a99b3382055542d967dca420df Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sun, 14 Feb 2021 16:12:47 -0800 Subject: 7745 --- html/baremetal/500text-screen.mu.html | 4 +- html/baremetal/boot.hex.html | 4 +- html/baremetal/shell/eval.mu.html | 1011 +++++++++++++++++++++++++++ html/baremetal/shell/gap-buffer.mu.html | 591 ++++++++++++++++ html/baremetal/shell/grapheme-stack.mu.html | 305 ++++++++ html/baremetal/shell/line.mu.html | 298 ++++++++ html/baremetal/shell/value-stack.mu.html | 304 ++++++++ html/baremetal/shell/value.mu.html | 326 +++++++++ html/baremetal/shell/word.mu.html | 784 +++++++++++++++++++++ 9 files changed, 3623 insertions(+), 4 deletions(-) create mode 100644 html/baremetal/shell/eval.mu.html create mode 100644 html/baremetal/shell/gap-buffer.mu.html create mode 100644 html/baremetal/shell/grapheme-stack.mu.html create mode 100644 html/baremetal/shell/line.mu.html create mode 100644 html/baremetal/shell/value-stack.mu.html create mode 100644 html/baremetal/shell/value.mu.html create mode 100644 html/baremetal/shell/word.mu.html (limited to 'html/baremetal') diff --git a/html/baremetal/500text-screen.mu.html b/html/baremetal/500text-screen.mu.html index 04bd8548..5c1b0944 100644 --- a/html/baremetal/500text-screen.mu.html +++ b/html/baremetal/500text-screen.mu.html @@ -165,9 +165,9 @@ if ('onhashchange' in window) { 107 break-if-< 108 abort "tried to print out of screen bounds" 109 } -110 var height-addr/eax: (addr int) <- get screen, height +110 var width-addr/eax: (addr int) <- get screen, width 111 var result/ecx: int <- copy y -112 result <- multiply *height-addr +112 result <- multiply *width-addr 113 result <- add x 114 return result 115 } diff --git a/html/baremetal/boot.hex.html b/html/baremetal/boot.hex.html index bbcd38e1..2cbaef0e 100644 --- a/html/baremetal/boot.hex.html +++ b/html/baremetal/boot.hex.html @@ -315,7 +315,7 @@ if ('onhashchange' in window) { 260 261 # For now, not bothering reprogramming the IRQ to not conflict with software 262 # exceptions. - 263 # https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode + 263 # https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode 264 # 265 # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a 266 # debugger. @@ -381,7 +381,7 @@ if ('onhashchange' in window) { 326 b0 20 # al <- 0x20 327 e6 20 # port 0x20 <- al 328 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax - 329 # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) + 329 # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) 330 e4 64 # al <- port 0x64 331 a8 01 # set zf if bit 0 (least significant) is not set 332 74 89 # jump to epilogue if 0 bit is not set [label] diff --git a/html/baremetal/shell/eval.mu.html b/html/baremetal/shell/eval.mu.html new file mode 100644 index 00000000..36b8d3f9 --- /dev/null +++ b/html/baremetal/shell/eval.mu.html @@ -0,0 +1,1011 @@ + + + + +Mu - baremetal/shell/eval.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/eval.mu +
+  1 # evaluator (and parser) for the Mu shell language
+  2 # inputs:
+  3 #   a list of lines, each a list of words, each an editable gap-buffer
+  4 #   end: a word to stop at
+  5 # output:
+  6 #   a stack of values to render that summarizes the result of evaluation until 'end'
+  7 
+  8 # Key features of the language:
+  9 #   words matching '=___' create bindings
+ 10 #   line boundaries clear the stack (but not bindings)
+ 11 #   { and } for grouping words
+ 12 #   break and loop for control flow within groups
+ 13 #   -> for conditionally skipping the next word or group
+ 14 
+ 15 # Example: Pushing numbers from 1 to n on the stack
+ 16 #
+ 17 #   3 =n
+ 18 #   { n 1 <     -> break n n 1- =n loop }
+ 19 #
+ 20 # Stack as we evaluate each word in the second line:
+ 21 #     3 1 false          3 3 2  3   1
+ 22 #       3                  3 3      2
+ 23 #                                   3
+ 24 
+ 25 # Rules beyond simple postfix:
+ 26 #   If the final word is `->`, clear stack
+ 27 #   If the final word is `break`, pop top of stack
+ 28 #
+ 29 #   `{` and `}` don't affect evaluation
+ 30 #   If the final word is `{` or `}`, clear stack (to suppress rendering it)
+ 31 #
+ 32 #   If `->` in middle and top of stack is falsy, skip next word or group
+ 33 #
+ 34 #   If `break` in middle executes, skip to next containing `}`
+ 35 #     If no containing `}`, clear stack (incomplete)
+ 36 #
+ 37 #   If `loop` in middle executes, skip to previous containing `{`
+ 38 #     If no containing `}`, clear stack (error)
+ 39 
+ 40 fn evaluate _in: (addr line), end: (addr word), out: (addr value-stack) {
+ 41   clear-value-stack out
+ 42   var line/eax: (addr line) <- copy _in
+ 43   var curr-ah/eax: (addr handle word) <- get line, data
+ 44   var curr/eax: (addr word) <- lookup *curr-ah
+ 45   evaluate-sub curr, end, out, 1/top-level
+ 46 }
+ 47 
+ 48 fn evaluate-sub _curr: (addr word), end: (addr word), out: (addr value-stack), top-level?: boolean {
+ 49   var curr/ecx: (addr word) <- copy _curr
+ 50   var curr-stream-storage: (stream byte 0x10)
+ 51   var curr-stream/edi: (addr stream byte) <- address curr-stream-storage
+ 52   $evaluate-sub:loop: {
+ 53     # safety net (should never hit)
+ 54     compare curr, 0
+ 55     break-if-=
+ 56     # pull next word in for parsing
+ 57     emit-word curr, curr-stream
+ 58 #?     {
+ 59 #?       clear-screen 0/screen
+ 60 #?       dump-stack out
+ 61 #?       var foo/eax: int <- render-word 0/screen, curr, 0/x, 0/y, 0/no-cursor
+ 62 #?       {
+ 63 #?         var key/eax: byte <- read-key 0/keyboard
+ 64 #?         compare key, 0
+ 65 #?         loop-if-=
+ 66 #?       }
+ 67 #?     }
+ 68     $evaluate-sub:process-word: {
+ 69       ### if curr-stream is an operator, perform it
+ 70       {
+ 71         var is-add?/eax: boolean <- stream-data-equal? curr-stream, "+"
+ 72         compare is-add?, 0/false
+ 73         break-if-=
+ 74         var _b/xmm0: float <- pop-number-from-value-stack out
+ 75         var b/xmm1: float <- copy _b
+ 76         var a/xmm0: float <- pop-number-from-value-stack out
+ 77         a <- add b
+ 78         push-number-to-value-stack out, a
+ 79         break $evaluate-sub:process-word
+ 80       }
+ 81       {
+ 82         var is-sub?/eax: boolean <- stream-data-equal? curr-stream, "-"
+ 83         compare is-sub?, 0/false
+ 84         break-if-=
+ 85         var _b/xmm0: float <- pop-number-from-value-stack out
+ 86         var b/xmm1: float <- copy _b
+ 87         var a/xmm0: float <- pop-number-from-value-stack out
+ 88         a <- subtract b
+ 89         push-number-to-value-stack out, a
+ 90         break $evaluate-sub:process-word
+ 91       }
+ 92       {
+ 93         var is-mul?/eax: boolean <- stream-data-equal? curr-stream, "*"
+ 94         compare is-mul?, 0/false
+ 95         break-if-=
+ 96         var _b/xmm0: float <- pop-number-from-value-stack out
+ 97         var b/xmm1: float <- copy _b
+ 98         var a/xmm0: float <- pop-number-from-value-stack out
+ 99         a <- multiply b
+100         push-number-to-value-stack out, a
+101         break $evaluate-sub:process-word
+102       }
+103       {
+104         var is-div?/eax: boolean <- stream-data-equal? curr-stream, "/"
+105         compare is-div?, 0/false
+106         break-if-=
+107         var _b/xmm0: float <- pop-number-from-value-stack out
+108         var b/xmm1: float <- copy _b
+109         var a/xmm0: float <- pop-number-from-value-stack out
+110         a <- divide b
+111         push-number-to-value-stack out, a
+112         break $evaluate-sub:process-word
+113       }
+114       {
+115         var is-sqrt?/eax: boolean <- stream-data-equal? curr-stream, "sqrt"
+116         compare is-sqrt?, 0/false
+117         break-if-=
+118         var a/xmm0: float <- pop-number-from-value-stack out
+119         a <- square-root a
+120         push-number-to-value-stack out, a
+121         break $evaluate-sub:process-word
+122       }
+123       {
+124         var is-lesser?/eax: boolean <- stream-data-equal? curr-stream, "<"
+125         compare is-lesser?, 0/false
+126         break-if-=
+127         var _b/xmm0: float <- pop-number-from-value-stack out
+128         var b/xmm1: float <- copy _b
+129         var a/xmm0: float <- pop-number-from-value-stack out
+130         compare a, b
+131         {
+132           break-if-float<
+133           push-boolean-to-value-stack out, 0/false
+134           break $evaluate-sub:process-word
+135         }
+136         push-boolean-to-value-stack out, 1/true
+137         break $evaluate-sub:process-word
+138       }
+139       {
+140         var is-greater?/eax: boolean <- stream-data-equal? curr-stream, ">"
+141         compare is-greater?, 0/false
+142         break-if-=
+143         var _b/xmm0: float <- pop-number-from-value-stack out
+144         var b/xmm1: float <- copy _b
+145         var a/xmm0: float <- pop-number-from-value-stack out
+146         compare a, b
+147         {
+148           break-if-float>
+149           push-boolean-to-value-stack out, 0/false
+150           break $evaluate-sub:process-word
+151         }
+152         push-boolean-to-value-stack out, 1/true
+153         break $evaluate-sub:process-word
+154       }
+155       {
+156         var is-equal?/eax: boolean <- stream-data-equal? curr-stream, "=="  # TODO support non-numbers
+157         compare is-equal?, 0/false
+158         break-if-=
+159         var _b/xmm0: float <- pop-number-from-value-stack out
+160         var b/xmm1: float <- copy _b
+161         var a/xmm0: float <- pop-number-from-value-stack out
+162         compare a, b
+163         {
+164           break-if-=
+165           push-boolean-to-value-stack out, 0/false
+166           break $evaluate-sub:process-word
+167         }
+168         push-boolean-to-value-stack out, 1/true
+169         break $evaluate-sub:process-word
+170       }
+171       ## control flow
+172       {
+173         var is-conditional?/eax: boolean <- stream-data-equal? curr-stream, "->"
+174         compare is-conditional?, 0/false
+175         break-if-=
+176         var a/eax: boolean <- pop-boolean-from-value-stack out
+177         compare a, 0/false
+178         {
+179           break-if-!=
+180           # if a is false, skip one word
+181           var next-word: (handle word)
+182           var next-word-ah/eax: (addr handle word) <- address next-word
+183           skip-word curr, end, next-word-ah
+184           var _curr/eax: (addr word) <- lookup *next-word-ah
+185           curr <- copy _curr
+186         }
+187         break $evaluate-sub:process-word
+188       }
+189       {
+190         var is-group-start?/eax: boolean <- stream-data-equal? curr-stream, "{"
+191         compare is-group-start?, 0/false
+192         break-if-=
+193         # if top-level? and this is the final word, clear the stack
+194         compare top-level?, 0/false
+195         break-if-= $evaluate-sub:process-word
+196         compare curr, end
+197         break-if-!= $evaluate-sub:process-word
+198         clear-value-stack out
+199         break $evaluate-sub:process-word
+200       }
+201       {
+202         var is-group-end?/eax: boolean <- stream-data-equal? curr-stream, "}"
+203         compare is-group-end?, 0/false
+204         break-if-=
+205         # if top-level? and this is the final word, clear the stack
+206         compare top-level?, 0/false
+207         break-if-= $evaluate-sub:process-word
+208         compare curr, end
+209         break-if-!= $evaluate-sub:process-word
+210         clear-value-stack out
+211         break $evaluate-sub:process-word
+212       }
+213       {
+214         var is-break?/eax: boolean <- stream-data-equal? curr-stream, "break"
+215         compare is-break?, 0/false
+216         break-if-=
+217         # scan ahead to containing '}'
+218         var next-word: (handle word)
+219         var next-word-ah/eax: (addr handle word) <- address next-word
+220         skip-rest-of-group curr, end, next-word-ah
+221         var _curr/eax: (addr word) <- lookup *next-word-ah
+222         curr <- copy _curr
+223         loop $evaluate-sub:loop
+224       }
+225       {
+226         var is-loop?/eax: boolean <- stream-data-equal? curr-stream, "loop"
+227         compare is-loop?, 0/false
+228         break-if-=
+229         # scan back to containing '{'
+230         var open-word: (handle word)
+231         var open-word-ah/edx: (addr handle word) <- address open-word
+232         scan-to-start-of-group curr, end, open-word-ah
+233         # scan ahead to the containing '}'; record that as next word to eval at
+234         var close-word: (handle word)
+235         var close-word-ah/ebx: (addr handle word) <- address close-word
+236         skip-rest-of-group curr, end, close-word-ah
+237         var _curr/eax: (addr word) <- lookup *close-word-ah
+238         curr <- copy _curr
+239         # now eval until getting there
+240         # TODO: can 'curr' be after 'end' at this point?
+241         var open/eax: (addr word) <- lookup *open-word-ah
+242         evaluate-sub open, curr, out, 0/nested
+243         loop $evaluate-sub:loop
+244       }
+245       ## TEMPORARY HACKS; we're trying to avoid turning this into Forth
+246       {
+247         var is-dup?/eax: boolean <- stream-data-equal? curr-stream, "dup"
+248         compare is-dup?, 0/false
+249         break-if-=
+250         # read src-val from out
+251         var out2/esi: (addr value-stack) <- copy out
+252         var top-addr/ecx: (addr int) <- get out2, top
+253         compare *top-addr, 0
+254         break-if-<=
+255         var data-ah/eax: (addr handle array value) <- get out2, data
+256         var data/eax: (addr array value) <- lookup *data-ah
+257         var top/ecx: int <- copy *top-addr
+258         top <- decrement
+259         var offset/edx: (offset value) <- compute-offset data, top
+260         var src-val/edx: (addr value) <- index data, offset
+261         # push a copy of it
+262         top <- increment
+263         var offset/ebx: (offset value) <- compute-offset data, top
+264         var target-val/ebx: (addr value) <- index data, offset
+265         copy-object src-val, target-val
+266         # commit
+267         var top-addr/ecx: (addr int) <- get out2, top
+268         increment *top-addr
+269         break $evaluate-sub:process-word
+270       }
+271       {
+272         var is-swap?/eax: boolean <- stream-data-equal? curr-stream, "swap"
+273         compare is-swap?, 0/false
+274         break-if-=
+275         # read top-val from out
+276         var out2/esi: (addr value-stack) <- copy out
+277         var top-addr/ecx: (addr int) <- get out2, top
+278         compare *top-addr, 0
+279         break-if-<=
+280         var data-ah/eax: (addr handle array value) <- get out2, data
+281         var data/eax: (addr array value) <- lookup *data-ah
+282         var top/ecx: int <- copy *top-addr
+283         top <- decrement
+284         var offset/edx: (offset value) <- compute-offset data, top
+285         var top-val/edx: (addr value) <- index data, offset
+286         # read next val from out
+287         top <- decrement
+288         var offset/ebx: (offset value) <- compute-offset data, top
+289         var pen-top-val/ebx: (addr value) <- index data, offset
+290         # swap
+291         var tmp: value
+292         var tmp-a/eax: (addr value) <- address tmp
+293         copy-object top-val, tmp-a
+294         copy-object pen-top-val, top-val
+295         copy-object tmp-a, pen-top-val
+296         break $evaluate-sub:process-word
+297       }
+298       ### if the word starts with a quote and ends with a quote, turn it into a string
+299       {
+300         rewind-stream curr-stream
+301         var start/eax: byte <- stream-first curr-stream
+302         compare start, 0x22/double-quote
+303         break-if-!=
+304         var end/eax: byte <- stream-final curr-stream
+305         compare end, 0x22/double-quote
+306         break-if-!=
+307         var h: (handle array byte)
+308         var s/eax: (addr handle array byte) <- address h
+309         unquote-stream-to-array curr-stream, s  # leak
+310         push-string-to-value-stack out, *s
+311         break $evaluate-sub:process-word
+312       }
+313       ### if the word starts with a '[' and ends with a ']', turn it into an array
+314       {
+315         rewind-stream curr-stream
+316         var start/eax: byte <- stream-first curr-stream
+317         compare start, 0x5b/open-bracket
+318         break-if-!=
+319         var end/eax: byte <- stream-final curr-stream
+320         compare end, 0x5d/close-bracket
+321         break-if-!=
+322         # wastefully create a new input string to strip quotes
+323         var h: (handle array value)
+324         var input-ah/eax: (addr handle array byte) <- address h
+325         unquote-stream-to-array curr-stream, input-ah  # leak
+326         # wastefully parse input into int-array
+327         # TODO: support parsing arrays of other types
+328         var input/eax: (addr array byte) <- lookup *input-ah
+329         var h2: (handle array int)
+330         var int-array-ah/esi: (addr handle array int) <- address h2
+331         parse-array-of-decimal-ints input, int-array-ah  # leak
+332         var _int-array/eax: (addr array int) <- lookup *int-array-ah
+333         var int-array/esi: (addr array int) <- copy _int-array
+334         var len/ebx: int <- length int-array
+335         # push value-array of same size as int-array
+336         var h3: (handle array value)
+337         var value-array-ah/eax: (addr handle array value) <- address h3
+338         populate value-array-ah, len
+339         push-array-to-value-stack out, *value-array-ah
+340         # copy int-array into value-array
+341         var _value-array/eax: (addr array value) <- lookup *value-array-ah
+342         var value-array/edi: (addr array value) <- copy _value-array
+343         var i/eax: int <- copy 0
+344         {
+345           compare i, len
+346           break-if->=
+347           var src-addr/ecx: (addr int) <- index int-array, i
+348           var src/ecx: int <- copy *src-addr
+349           var src-f/xmm0: float <- convert src
+350           var dest-offset/edx: (offset value) <- compute-offset value-array, i
+351           var dest-val/edx: (addr value) <- index value-array, dest-offset
+352           var dest/edx: (addr float) <- get dest-val, number-data
+353           copy-to *dest, src-f
+354           i <- increment
+355           loop
+356         }
+357         break $evaluate-sub:process-word
+358       }
+359       ### otherwise assume it's a literal number and push it (can't parse floats yet)
+360       {
+361         var n/eax: int <- parse-decimal-int-from-stream curr-stream
+362         var n-f/xmm0: float <- convert n
+363         push-number-to-value-stack out, n-f
+364       }
+365     }
+366     # termination check
+367     compare curr, end
+368     break-if-=
+369     # update
+370     var next-word-ah/edx: (addr handle word) <- get curr, next
+371     var _curr/eax: (addr word) <- lookup *next-word-ah
+372     curr <- copy _curr
+373     #
+374     loop
+375   }
+376 }
+377 
+378 fn skip-word _curr: (addr word), end: (addr word), out: (addr handle word) {
+379   var curr/eax: (addr word) <- copy _curr
+380   var bracket-count/ecx: int <- copy 0
+381   var result-ah/esi: (addr handle word) <- get curr, next
+382   {
+383     var result-val/eax: (addr word) <- lookup *result-ah
+384     compare result-val, end
+385     break-if-=
+386     {
+387       var open?/eax: boolean <- word-equal? result-val, "{"
+388       compare open?, 0/false
+389       break-if-=
+390       bracket-count <- increment
+391     }
+392     {
+393       var close?/eax: boolean <- word-equal? result-val, "}"
+394       compare close?, 0/false
+395       break-if-=
+396       bracket-count <- decrement
+397       compare bracket-count, 0
+398       {
+399         break-if->=
+400         abort "'->' cannot be final word in a {} group"  # TODO: error-handling
+401       }
+402     }
+403     compare bracket-count, 0
+404     break-if-=
+405     result-ah <- get result-val, next
+406     loop
+407   }
+408   copy-object result-ah, out
+409 }
+410 
+411 fn skip-rest-of-group _curr: (addr word), end: (addr word), out: (addr handle word) {
+412   var curr/eax: (addr word) <- copy _curr
+413   var bracket-count/ecx: int <- copy 0
+414   var result-ah/esi: (addr handle word) <- get curr, next
+415   $skip-rest-of-group:loop: {
+416     var result-val/eax: (addr word) <- lookup *result-ah
+417     compare result-val, end
+418     break-if-=
+419     {
+420       var open?/eax: boolean <- word-equal? result-val, "{"
+421       compare open?, 0/false
+422       break-if-=
+423       bracket-count <- increment
+424     }
+425     {
+426       var close?/eax: boolean <- word-equal? result-val, "}"
+427       compare close?, 0/false
+428       break-if-=
+429       compare bracket-count, 0
+430       break-if-= $skip-rest-of-group:loop
+431       bracket-count <- decrement
+432     }
+433     result-ah <- get result-val, next
+434     loop
+435   }
+436   copy-object result-ah, out
+437 }
+438 
+439 fn scan-to-start-of-group _curr: (addr word), end: (addr word), out: (addr handle word) {
+440   var curr/eax: (addr word) <- copy _curr
+441   var bracket-count/ecx: int <- copy 0
+442   var result-ah/esi: (addr handle word) <- get curr, prev
+443   $scan-to-start-of-group:loop: {
+444     var result-val/eax: (addr word) <- lookup *result-ah
+445     compare result-val, end
+446     break-if-=
+447     {
+448       var open?/eax: boolean <- word-equal? result-val, "{"
+449       compare open?, 0/false
+450       break-if-=
+451       compare bracket-count, 0
+452       break-if-= $scan-to-start-of-group:loop
+453       bracket-count <- increment
+454     }
+455     {
+456       var close?/eax: boolean <- word-equal? result-val, "}"
+457       compare close?, 0/false
+458       break-if-=
+459       bracket-count <- decrement
+460     }
+461     result-ah <- get result-val, prev
+462     loop
+463   }
+464   copy-object result-ah, out
+465 }
+466 
+467 fn test-eval-arithmetic {
+468   # in
+469   var in-storage: line
+470   var in/esi: (addr line) <- address in-storage
+471   parse-line "1 1 +", in
+472   # end
+473   var w-ah/eax: (addr handle word) <- get in, data
+474   var end-h: (handle word)
+475   var end-ah/ecx: (addr handle word) <- address end-h
+476   final-word w-ah, end-ah
+477   var end/eax: (addr word) <- lookup *end-ah
+478   # out
+479   var out-storage: value-stack
+480   var out/edi: (addr value-stack) <- address out-storage
+481   initialize-value-stack out, 8
+482   #
+483   evaluate in, end, out
+484   #
+485   var len/eax: int <- value-stack-length out
+486   check-ints-equal len, 1, "F - test-eval-arithmetic stack size"
+487   var n/xmm0: float <- pop-number-from-value-stack out
+488   var n2/eax: int <- convert n
+489   check-ints-equal n2, 2, "F - test-eval-arithmetic result"
+490 }
+491 
+492 fn test-eval-string {
+493   # in
+494   var in-storage: line
+495   var in/esi: (addr line) <- address in-storage
+496   parse-line "\"abc\"", in  # TODO support spaces within strings
+497   # end
+498   var w-ah/eax: (addr handle word) <- get in, data
+499   var end-h: (handle word)
+500   var end-ah/ecx: (addr handle word) <- address end-h
+501   final-word w-ah, end-ah
+502   var end/eax: (addr word) <- lookup *end-ah
+503   # out
+504   var out-storage: value-stack
+505   var out/edi: (addr value-stack) <- address out-storage
+506   initialize-value-stack out, 8
+507   #
+508   evaluate in, end, out
+509   #
+510   var len/eax: int <- value-stack-length out
+511   check-ints-equal len, 1, "F - test-eval-string stack size"
+512   var out-data-ah/eax: (addr handle array value) <- get out, data
+513   var out-data/eax: (addr array value) <- lookup *out-data-ah
+514   var v/eax: (addr value) <- index out-data, 0
+515   var type/ecx: (addr int) <- get v, type
+516   check-ints-equal *type, 1/text, "F - test-eval-string type"
+517   var text-ah/eax: (addr handle array byte) <- get v, text-data
+518   var text/eax: (addr array byte) <- lookup *text-ah
+519   check-strings-equal text, "abc", "F - test-eval-string result"
+520 }
+521 
+522 fn test-eval-compare-lesser {
+523   # in
+524   var in-storage: line
+525   var in/esi: (addr line) <- address in-storage
+526   parse-line "1 2 <", in
+527   # end
+528   var w-ah/eax: (addr handle word) <- get in, data
+529   var end-h: (handle word)
+530   var end-ah/ecx: (addr handle word) <- address end-h
+531   final-word w-ah, end-ah
+532   var end/eax: (addr word) <- lookup *end-ah
+533   # out
+534   var out-storage: value-stack
+535   var out/edi: (addr value-stack) <- address out-storage
+536   initialize-value-stack out, 8
+537   #
+538   evaluate in, end, out
+539   #
+540   var len/eax: int <- value-stack-length out
+541   check-ints-equal len, 1, "F - test-eval-compare-lesser stack size"
+542   var result/eax: boolean <- pop-boolean-from-value-stack out
+543   check result, "F - test-eval-compare-lesser result"
+544 }
+545 
+546 fn test-eval-compare-greater {
+547   # in
+548   var in-storage: line
+549   var in/esi: (addr line) <- address in-storage
+550   parse-line "2 1 >", in
+551   # end
+552   var w-ah/eax: (addr handle word) <- get in, data
+553   var end-h: (handle word)
+554   var end-ah/ecx: (addr handle word) <- address end-h
+555   final-word w-ah, end-ah
+556   var end/eax: (addr word) <- lookup *end-ah
+557   # out
+558   var out-storage: value-stack
+559   var out/edi: (addr value-stack) <- address out-storage
+560   initialize-value-stack out, 8
+561   #
+562   evaluate in, end, out
+563   #
+564   var len/eax: int <- value-stack-length out
+565   check-ints-equal len, 1, "F - test-eval-compare-greater stack size"
+566   var result/eax: boolean <- pop-boolean-from-value-stack out
+567   check result, "F - test-eval-compare-greater result"
+568 }
+569 
+570 fn test-eval-compare-equal-fails {
+571   # in
+572   var in-storage: line
+573   var in/esi: (addr line) <- address in-storage
+574   parse-line "1 2 ==", in
+575   # end
+576   var w-ah/eax: (addr handle word) <- get in, data
+577   var end-h: (handle word)
+578   var end-ah/ecx: (addr handle word) <- address end-h
+579   final-word w-ah, end-ah
+580   var end/eax: (addr word) <- lookup *end-ah
+581   # out
+582   var out-storage: value-stack
+583   var out/edi: (addr value-stack) <- address out-storage
+584   initialize-value-stack out, 8
+585   #
+586   evaluate in, end, out
+587   #
+588   var len/eax: int <- value-stack-length out
+589   check-ints-equal len, 1, "F - test-eval-compare-equal-fails stack size"
+590   var result/eax: boolean <- pop-boolean-from-value-stack out
+591   check-not result, "F - test-eval-compare-equal-fails result"
+592 }
+593 
+594 fn test-eval-compare-equal {
+595   # in
+596   var in-storage: line
+597   var in/esi: (addr line) <- address in-storage
+598   parse-line "2 2 ==", in
+599   # end
+600   var w-ah/eax: (addr handle word) <- get in, data
+601   var end-h: (handle word)
+602   var end-ah/ecx: (addr handle word) <- address end-h
+603   final-word w-ah, end-ah
+604   var end/eax: (addr word) <- lookup *end-ah
+605   # out
+606   var out-storage: value-stack
+607   var out/edi: (addr value-stack) <- address out-storage
+608   initialize-value-stack out, 8
+609   #
+610   evaluate in, end, out
+611   #
+612   var len/eax: int <- value-stack-length out
+613   check-ints-equal len, 1, "F - test-eval-compare-equal stack size"
+614   var result/eax: boolean <- pop-boolean-from-value-stack out
+615   check result, "F - test-eval-compare-equal result"
+616 }
+617 
+618 fn test-eval-conditional {
+619   # in
+620   var in-storage: line
+621   var in/esi: (addr line) <- address in-storage
+622   parse-line "1 2 < -> 3", in
+623   # end
+624   var w-ah/eax: (addr handle word) <- get in, data
+625   var end-h: (handle word)
+626   var end-ah/ecx: (addr handle word) <- address end-h
+627   final-word w-ah, end-ah
+628   var end/eax: (addr word) <- lookup *end-ah
+629   # out
+630   var out-storage: value-stack
+631   var out/edi: (addr value-stack) <- address out-storage
+632   initialize-value-stack out, 8
+633   #
+634   evaluate in, end, out
+635   #
+636   var len/eax: int <- value-stack-length out
+637   check-ints-equal len, 1, "F - test-eval-conditional stack size"
+638   var n/xmm0: float <- pop-number-from-value-stack out
+639   var n2/eax: int <- convert n
+640   check-ints-equal n2, 3, "F - test-eval-conditional result"
+641 }
+642 
+643 # if top of stack is false, `->` skips one word
+644 fn test-eval-conditional-skipped {
+645   # in
+646   var in-storage: line
+647   var in/esi: (addr line) <- address in-storage
+648   parse-line "1 2 > -> 3", in
+649   # end
+650   var w-ah/eax: (addr handle word) <- get in, data
+651   var end-h: (handle word)
+652   var end-ah/ecx: (addr handle word) <- address end-h
+653   final-word w-ah, end-ah
+654   var end/eax: (addr word) <- lookup *end-ah
+655   # out
+656   var out-storage: value-stack
+657   var out/edi: (addr value-stack) <- address out-storage
+658   initialize-value-stack out, 8
+659   #
+660   evaluate in, end, out
+661   #
+662   var len/eax: int <- value-stack-length out
+663   check-ints-equal len, 0, "F - test-eval-conditional-skipped stack size"
+664 }
+665 
+666 # curlies have no effect in isolation
+667 fn test-eval-group {
+668   # in
+669   var in-storage: line
+670   var in/esi: (addr line) <- address in-storage
+671   parse-line "{ 1 } 1 +", in
+672   # end
+673   var w-ah/eax: (addr handle word) <- get in, data
+674   var end-h: (handle word)
+675   var end-ah/ecx: (addr handle word) <- address end-h
+676   final-word w-ah, end-ah
+677   var end/eax: (addr word) <- lookup *end-ah
+678   # out
+679   var out-storage: value-stack
+680   var out/edi: (addr value-stack) <- address out-storage
+681   initialize-value-stack out, 8
+682   #
+683   evaluate in, end, out
+684   #
+685   var len/eax: int <- value-stack-length out
+686   check-ints-equal len, 1, "F - test-eval-group stack size"
+687   var n/xmm0: float <- pop-number-from-value-stack out
+688   var n2/eax: int <- convert n
+689   check-ints-equal n2, 2, "F - test-eval-group result"
+690 }
+691 
+692 fn test-eval-group-open-at-end {
+693   # in
+694   var in-storage: line
+695   var in/esi: (addr line) <- address in-storage
+696   parse-line "1 1 + {", in
+697   # end
+698   var w-ah/eax: (addr handle word) <- get in, data
+699   var end-h: (handle word)
+700   var end-ah/ecx: (addr handle word) <- address end-h
+701   final-word w-ah, end-ah
+702   var end/eax: (addr word) <- lookup *end-ah
+703   # out
+704   var out-storage: value-stack
+705   var out/edi: (addr value-stack) <- address out-storage
+706   initialize-value-stack out, 8
+707   #
+708   evaluate in, end, out
+709   #
+710   var len/eax: int <- value-stack-length out
+711   check-ints-equal len, 0, "F - test-eval-group-open-at-end stack size"
+712 }
+713 
+714 fn test-eval-group-close-at-end {
+715   # in
+716   var in-storage: line
+717   var in/esi: (addr line) <- address in-storage
+718   parse-line "{ 1 1 + }", in
+719   # end
+720   var w-ah/eax: (addr handle word) <- get in, data
+721   var end-h: (handle word)
+722   var end-ah/ecx: (addr handle word) <- address end-h
+723   final-word w-ah, end-ah
+724   var end/eax: (addr word) <- lookup *end-ah
+725   # out
+726   var out-storage: value-stack
+727   var out/edi: (addr value-stack) <- address out-storage
+728   initialize-value-stack out, 8
+729   #
+730   evaluate in, end, out
+731   #
+732   var len/eax: int <- value-stack-length out
+733   check-ints-equal len, 0, "F - test-eval-group-close-at-end stack size"
+734 }
+735 
+736 fn test-eval-conditional-skips-group {
+737   # in
+738   var in-storage: line
+739   var in/esi: (addr line) <- address in-storage
+740   parse-line "1 2 > -> { 3 } 9", in
+741   # end
+742   var w-ah/eax: (addr handle word) <- get in, data
+743   var end-h: (handle word)
+744   var end-ah/ecx: (addr handle word) <- address end-h
+745   final-word w-ah, end-ah
+746   var end/eax: (addr word) <- lookup *end-ah
+747   # out
+748   var out-storage: value-stack
+749   var out/edi: (addr value-stack) <- address out-storage
+750   initialize-value-stack out, 8
+751   #
+752   evaluate in, end, out
+753   # out contains just the final sentinel '9'
+754   var len/eax: int <- value-stack-length out
+755   check-ints-equal len, 1, "F - test-eval-conditional-skips-group stack size"
+756 }
+757 
+758 fn test-eval-conditional-skips-nested-group {
+759   # in
+760   var in-storage: line
+761   var in/esi: (addr line) <- address in-storage
+762   parse-line "1 2 > -> { { 3 } 4 } 9", in
+763   # end
+764   var w-ah/eax: (addr handle word) <- get in, data
+765   var end-h: (handle word)
+766   var end-ah/ecx: (addr handle word) <- address end-h
+767   final-word w-ah, end-ah
+768   var end/eax: (addr word) <- lookup *end-ah
+769   # out
+770   var out-storage: value-stack
+771   var out/edi: (addr value-stack) <- address out-storage
+772   initialize-value-stack out, 8
+773   #
+774   evaluate in, end, out
+775   # out contains just the final sentinel '9'
+776   var len/eax: int <- value-stack-length out
+777   check-ints-equal len, 1, "F - test-eval-conditional-skips-nested-group stack size"
+778 }
+779 
+780 # TODO: test error-handling on:
+781 #   1 2 > -> }
+782 
+783 # break skips to next containing `}`
+784 fn test-eval-break {
+785   # in
+786   var in-storage: line
+787   var in/esi: (addr line) <- address in-storage
+788   parse-line "3 { 4 break 5 } +", in
+789   # end
+790   var w-ah/eax: (addr handle word) <- get in, data
+791   var end-h: (handle word)
+792   var end-ah/ecx: (addr handle word) <- address end-h
+793   final-word w-ah, end-ah
+794   var end/eax: (addr word) <- lookup *end-ah
+795   # out
+796   var out-storage: value-stack
+797   var out/edi: (addr value-stack) <- address out-storage
+798   initialize-value-stack out, 8
+799   #
+800   evaluate in, end, out
+801   # result is 3+4, not 4+5
+802   var len/eax: int <- value-stack-length out
+803   check-ints-equal len, 1, "F - test-eval-break stack size"
+804   var n/xmm0: float <- pop-number-from-value-stack out
+805   var n2/eax: int <- convert n
+806   check-ints-equal n2, 7, "F - test-eval-break result"
+807 }
+808 
+809 fn test-eval-break-nested {
+810   # in
+811   var in-storage: line
+812   var in/esi: (addr line) <- address in-storage
+813   parse-line "3 { 4 break { 5 } 6 } +", in
+814   # end
+815   var w-ah/eax: (addr handle word) <- get in, data
+816   var end-h: (handle word)
+817   var end-ah/ecx: (addr handle word) <- address end-h
+818   final-word w-ah, end-ah
+819   var end/eax: (addr word) <- lookup *end-ah
+820   # out
+821   var out-storage: value-stack
+822   var out/edi: (addr value-stack) <- address out-storage
+823   initialize-value-stack out, 8
+824   #
+825   evaluate in, end, out
+826   # result is 3+4, skipping remaining numbers
+827   var len/eax: int <- value-stack-length out
+828   check-ints-equal len, 1, "F - test-eval-break-nested stack size"
+829   var n/xmm0: float <- pop-number-from-value-stack out
+830   var n2/eax: int <- convert n
+831   check-ints-equal n2, 7, "F - test-eval-break-nested result"
+832 }
+833 
+834 #? 1 2 3 4 6 5 { <       -> break loop }
+835 #?  1 2 3 4 6 5   false            2
+836 #?    1 2 3 4 6   4                1
+837 #?      1 2 3 4   3
+838 #?        1 2 3   2
+839 #?          1 2   1
+840 #?            1
+841 
+842 #? 1 2 3 4 { 3 ==     -> return loop }
+843 #?  1 2 3 4   3 false            2      => 3
+844 #?    1 2 3   4 3                1
+845 #?      1 2   3 2
+846 #?        1   2 1
+847 #?            1
+848 
+849 # loop skips to previous containing `{` and continues evaluating until control
+850 # leaves the group
+851 fn test-eval-loop {
+852   # in
+853   var in-storage: line
+854   var in/esi: (addr line) <- address in-storage
+855   parse-line "1 2 4 3 { < -> break loop } 9", in
+856   # end
+857   var w-ah/eax: (addr handle word) <- get in, data
+858   var end-h: (handle word)
+859   var end-ah/ecx: (addr handle word) <- address end-h
+860   final-word w-ah, end-ah
+861   var end/eax: (addr word) <- lookup *end-ah
+862   # out
+863   var out-storage: value-stack
+864   var out/edi: (addr value-stack) <- address out-storage
+865   initialize-value-stack out, 8
+866   #
+867   evaluate in, end, out
+868   # evaluation order: 1 2 4 3 { < -> loop { < -> break 9
+869   # stack contents: 9
+870   var len/eax: int <- value-stack-length out
+871   check-ints-equal len, 1, "F - test-eval-loop stack size"
+872 }
+873 
+874 fn test-eval-loop-2 {
+875   # in
+876   var in-storage: line
+877   var in/esi: (addr line) <- address in-storage
+878   parse-line "1 2 4 3 { 4 == -> break loop } 9", in
+879   # end
+880   var w-ah/eax: (addr handle word) <- get in, data
+881   var end-h: (handle word)
+882   var end-ah/ecx: (addr handle word) <- address end-h
+883   final-word w-ah, end-ah
+884   var end/eax: (addr word) <- lookup *end-ah
+885   # out
+886   var out-storage: value-stack
+887   var out/edi: (addr value-stack) <- address out-storage
+888   initialize-value-stack out, 8
+889   #
+890   evaluate in, end, out
+891   # evaluation order: 1 2 4 3 { 4 == -> loop { 4 == -> break 9
+892   # stack contents: 1 2 9
+893 #?   dump-stack out
+894   var len/eax: int <- value-stack-length out
+895 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, len, 0xc/red, 0/black
+896   check-ints-equal len, 3, "F - test-eval-loop-2 stack size"
+897 }
+898 
+899 fn test-eval-loop-conditional {
+900   # in
+901   var in-storage: line
+902   var in/esi: (addr line) <- address in-storage
+903   parse-line "1 2 3 { 3 == -> loop } 9", in
+904   # end
+905   var w-ah/eax: (addr handle word) <- get in, data
+906   var end-h: (handle word)
+907   var end-ah/ecx: (addr handle word) <- address end-h
+908   final-word w-ah, end-ah
+909   var end/eax: (addr word) <- lookup *end-ah
+910   # out
+911   var out-storage: value-stack
+912   var out/edi: (addr value-stack) <- address out-storage
+913   initialize-value-stack out, 8
+914   #
+915   evaluate in, end, out
+916   # evaluation order: 1 2 3 { 3 == -> loop { 3 == -> 9
+917   # stack contents: 1 9
+918 #?   dump-stack out
+919   var len/eax: int <- value-stack-length out
+920 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, len, 0xc/red, 0/black
+921   check-ints-equal len, 2, "F - test-eval-loop-2-conditional stack size"
+922 }
+923 
+924 fn test-eval-loop-with-words-after-in-group {
+925   # in
+926   var in-storage: line
+927   var in/esi: (addr line) <- address in-storage
+928   parse-line "1 2 3 { 3 == -> loop 37 } 9", in
+929   # end
+930   var w-ah/eax: (addr handle word) <- get in, data
+931   var end-h: (handle word)
+932   var end-ah/ecx: (addr handle word) <- address end-h
+933   final-word w-ah, end-ah
+934   var end/eax: (addr word) <- lookup *end-ah
+935   # out
+936   var out-storage: value-stack
+937   var out/edi: (addr value-stack) <- address out-storage
+938   initialize-value-stack out, 8
+939   #
+940   evaluate in, end, out
+941   # evaluation order: 1 2 3 { 3 == -> loop { 3 == -> 37 } 9
+942   # stack contents: 1 37 9
+943 #?   dump-stack out
+944   var len/eax: int <- value-stack-length out
+945 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, len, 0xc/red, 0/black
+946   check-ints-equal len, 3, "F - test-eval-loop-with-words-after-in-group stack size"
+947 }
+
+ + + diff --git a/html/baremetal/shell/gap-buffer.mu.html b/html/baremetal/shell/gap-buffer.mu.html new file mode 100644 index 00000000..c970b830 --- /dev/null +++ b/html/baremetal/shell/gap-buffer.mu.html @@ -0,0 +1,591 @@ + + + + +Mu - baremetal/shell/gap-buffer.mu + + + + + + + + + + +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 }
+
+ + + diff --git a/html/baremetal/shell/grapheme-stack.mu.html b/html/baremetal/shell/grapheme-stack.mu.html new file mode 100644 index 00000000..b77ff59d --- /dev/null +++ b/html/baremetal/shell/grapheme-stack.mu.html @@ -0,0 +1,305 @@ + + + + +Mu - baremetal/shell/grapheme-stack.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/grapheme-stack.mu +
+  1 # grapheme stacks are the smallest unit of editable text
+  2 # they are typically rendered horizontally
+  3 
+  4 type grapheme-stack {
+  5   data: (handle array grapheme)
+  6   top: int
+  7 }
+  8 
+  9 fn initialize-grapheme-stack _self: (addr grapheme-stack), n: int {
+ 10   var self/esi: (addr grapheme-stack) <- copy _self
+ 11   var d/edi: (addr handle array grapheme) <- get self, data
+ 12   populate d, n
+ 13   var top/eax: (addr int) <- get self, top
+ 14   copy-to *top, 0
+ 15 }
+ 16 
+ 17 fn clear-grapheme-stack _self: (addr grapheme-stack) {
+ 18   var self/esi: (addr grapheme-stack) <- copy _self
+ 19   var top/eax: (addr int) <- get self, top
+ 20   copy-to *top, 0
+ 21 }
+ 22 
+ 23 fn grapheme-stack-empty? _self: (addr grapheme-stack) -> _/eax: boolean {
+ 24   var self/esi: (addr grapheme-stack) <- copy _self
+ 25   var top/eax: (addr int) <- get self, top
+ 26   compare *top, 0
+ 27   {
+ 28     break-if-!=
+ 29     return 1/true
+ 30   }
+ 31   return 0/false
+ 32 }
+ 33 
+ 34 fn push-grapheme-stack _self: (addr grapheme-stack), _val: grapheme {
+ 35   var self/esi: (addr grapheme-stack) <- copy _self
+ 36   var top-addr/ecx: (addr int) <- get self, top
+ 37   var data-ah/edx: (addr handle array grapheme) <- get self, data
+ 38   var data/eax: (addr array grapheme) <- lookup *data-ah
+ 39   var top/edx: int <- copy *top-addr
+ 40   var dest-addr/edx: (addr grapheme) <- index data, top
+ 41   var val/eax: grapheme <- copy _val
+ 42   copy-to *dest-addr, val
+ 43   add-to *top-addr, 1
+ 44 }
+ 45 
+ 46 fn pop-grapheme-stack _self: (addr grapheme-stack) -> _/eax: grapheme {
+ 47   var self/esi: (addr grapheme-stack) <- copy _self
+ 48   var top-addr/ecx: (addr int) <- get self, top
+ 49   {
+ 50     compare *top-addr, 0
+ 51     break-if->
+ 52     return -1
+ 53   }
+ 54   subtract-from *top-addr, 1
+ 55   var data-ah/edx: (addr handle array grapheme) <- get self, data
+ 56   var data/eax: (addr array grapheme) <- lookup *data-ah
+ 57   var top/edx: int <- copy *top-addr
+ 58   var result-addr/eax: (addr grapheme) <- index data, top
+ 59   return *result-addr
+ 60 }
+ 61 
+ 62 fn copy-grapheme-stack _src: (addr grapheme-stack), dest: (addr grapheme-stack) {
+ 63   var src/esi: (addr grapheme-stack) <- copy _src
+ 64   var data-ah/edi: (addr handle array grapheme) <- get src, data
+ 65   var _data/eax: (addr array grapheme) <- lookup *data-ah
+ 66   var data/edi: (addr array grapheme) <- copy _data
+ 67   var top-addr/ecx: (addr int) <- get src, top
+ 68   var i/eax: int <- copy 0
+ 69   {
+ 70     compare i, *top-addr
+ 71     break-if->=
+ 72     var g/edx: (addr grapheme) <- index data, i
+ 73     push-grapheme-stack dest, *g
+ 74     i <- increment
+ 75     loop
+ 76   }
+ 77 }
+ 78 
+ 79 # dump stack to screen from bottom to top
+ 80 # colors hardcoded
+ 81 fn render-stack-from-bottom screen: (addr screen), _self: (addr grapheme-stack), x: int, y: int -> _/eax: int {
+ 82   var self/esi: (addr grapheme-stack) <- copy _self
+ 83   var data-ah/edi: (addr handle array grapheme) <- get self, data
+ 84   var _data/eax: (addr array grapheme) <- lookup *data-ah
+ 85   var data/edi: (addr array grapheme) <- copy _data
+ 86   var top-addr/ecx: (addr int) <- get self, top
+ 87   var i/eax: int <- copy 0
+ 88   {
+ 89     compare i, *top-addr
+ 90     break-if->=
+ 91     var g/edx: (addr grapheme) <- index data, i
+ 92     draw-grapheme screen, *g, x, y, 3/fg=cyan, 0/bg
+ 93     i <- increment
+ 94     increment x  # assume left to right
+ 95     loop
+ 96   }
+ 97   return x
+ 98 }
+ 99 
+100 # dump stack to screen from top to bottom
+101 # optionally render a 'cursor' with the top grapheme
+102 fn render-stack-from-top screen: (addr screen), _self: (addr grapheme-stack), x: int, y: int, render-cursor?: boolean -> _/eax: int {
+103   var self/esi: (addr grapheme-stack) <- copy _self
+104   var data-ah/edi: (addr handle array grapheme) <- get self, data
+105   var _data/eax: (addr array grapheme) <- lookup *data-ah
+106   var data/edi: (addr array grapheme) <- copy _data
+107   var top-addr/ecx: (addr int) <- get self, top
+108   var i/eax: int <- copy *top-addr
+109   i <- decrement
+110   # if render-cursor?, peel off first iteration
+111   {
+112     compare render-cursor?, 0/false
+113     break-if-=
+114     compare i, 0
+115     break-if-<
+116     var g/edx: (addr grapheme) <- index data, i
+117     draw-grapheme screen, *g, x, y, 3/fg=cyan, 7/bg=cursor
+118     i <- decrement
+119     increment x  # assume left to right
+120   }
+121   # remaining iterations
+122   {
+123     compare i, 0
+124     break-if-<
+125     var g/edx: (addr grapheme) <- index data, i
+126     draw-grapheme screen, *g, x, y, 3/fg=cyan, 0/bg
+127     i <- decrement
+128     increment x  # assume left to right
+129     loop
+130   }
+131   return x
+132 }
+133 
+134 fn test-render-grapheme-stack {
+135   # setup: gs = "abc"
+136   var gs-storage: grapheme-stack
+137   var gs/edi: (addr grapheme-stack) <- address gs-storage
+138   initialize-grapheme-stack gs, 5
+139   var g/eax: grapheme <- copy 0x61/a
+140   push-grapheme-stack gs, g
+141   g <- copy 0x62/b
+142   push-grapheme-stack gs, g
+143   g <- copy 0x63/c
+144   push-grapheme-stack gs, g
+145   # setup: screen
+146   var screen-on-stack: screen
+147   var screen/esi: (addr screen) <- address screen-on-stack
+148   initialize-screen screen, 5, 4
+149   #
+150   var x/eax: int <- render-stack-from-bottom screen, gs, 0/x, 0/y
+151   check-screen-row screen, 0/y, "abc ", "F - test-render-grapheme-stack from bottom"
+152   check-ints-equal x, 3, "F - test-render-grapheme-stack from bottom: result"
+153   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "   ", "F - test-render-grapheme-stack from bottom: bg"
+154   #
+155   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 1/y, 0/cursor=false
+156   check-screen-row screen, 1/y, "cba ", "F - test-render-grapheme-stack from top without cursor"
+157   check-ints-equal x, 3, "F - test-render-grapheme-stack from top without cursor: result"
+158   check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "   ", "F - test-render-grapheme-stack from top without cursor: bg"
+159   #
+160   var x/eax: int <- render-stack-from-top screen, gs, 0/x, 2/y, 1/cursor=true
+161   check-screen-row screen, 2/y, "cba ", "F - test-render-grapheme-stack from top with cursor"
+162   check-ints-equal x, 3, "F - test-render-grapheme-stack from top without cursor: result"
+163   check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "|   ", "F - test-render-grapheme-stack from top with cursor: bg"
+164 }
+165 
+166 # compare from bottom
+167 # beware: modifies 'stream', which must be disposed of after a false result
+168 fn prefix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
+169   var self/esi: (addr grapheme-stack) <- copy _self
+170   var data-ah/edi: (addr handle array grapheme) <- get self, data
+171   var _data/eax: (addr array grapheme) <- lookup *data-ah
+172   var data/edi: (addr array grapheme) <- copy _data
+173   var top-addr/ecx: (addr int) <- get self, top
+174   var i/ebx: int <- copy 0
+175   {
+176     compare i, *top-addr
+177     break-if->=
+178     # if curr != expected, return false
+179     {
+180       var curr-a/edx: (addr grapheme) <- index data, i
+181       var expected/eax: grapheme <- read-grapheme s
+182       {
+183         compare expected, *curr-a
+184         break-if-=
+185         return 0/false
+186       }
+187     }
+188     i <- increment
+189     loop
+190   }
+191   return 1   # true
+192 }
+193 
+194 # compare from bottom
+195 # beware: modifies 'stream', which must be disposed of after a false result
+196 fn suffix-match? _self: (addr grapheme-stack), s: (addr stream byte) -> _/eax: boolean {
+197   var self/esi: (addr grapheme-stack) <- copy _self
+198   var data-ah/edi: (addr handle array grapheme) <- get self, data
+199   var _data/eax: (addr array grapheme) <- lookup *data-ah
+200   var data/edi: (addr array grapheme) <- copy _data
+201   var top-addr/eax: (addr int) <- get self, top
+202   var i/ebx: int <- copy *top-addr
+203   i <- decrement
+204   {
+205     compare i, 0
+206     break-if-<
+207     {
+208       var curr-a/edx: (addr grapheme) <- index data, i
+209       var expected/eax: grapheme <- read-grapheme s
+210       # if curr != expected, return false
+211       {
+212         compare expected, *curr-a
+213         break-if-=
+214         return 0/false
+215       }
+216     }
+217     i <- decrement
+218     loop
+219   }
+220   return 1   # true
+221 }
+222 
+223 fn grapheme-stack-is-decimal-integer? _self: (addr grapheme-stack) -> _/eax: boolean {
+224   var self/esi: (addr grapheme-stack) <- copy _self
+225   var data-ah/eax: (addr handle array grapheme) <- get self, data
+226   var _data/eax: (addr array grapheme) <- lookup *data-ah
+227   var data/edx: (addr array grapheme) <- copy _data
+228   var top-addr/ecx: (addr int) <- get self, top
+229   var i/ebx: int <- copy 0
+230   var result/eax: boolean <- copy 1/true
+231   $grapheme-stack-is-integer?:loop: {
+232     compare i, *top-addr
+233     break-if->=
+234     var g/edx: (addr grapheme) <- index data, i
+235     result <- is-decimal-digit? *g
+236     compare result, 0/false
+237     break-if-=
+238     i <- increment
+239     loop
+240   }
+241   return result
+242 }
+
+ + + diff --git a/html/baremetal/shell/line.mu.html b/html/baremetal/shell/line.mu.html new file mode 100644 index 00000000..c106a0ea --- /dev/null +++ b/html/baremetal/shell/line.mu.html @@ -0,0 +1,298 @@ + + + + +Mu - baremetal/shell/line.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/line.mu +
+  1 type line {
+  2   name: (handle array byte)
+  3   data: (handle word)
+  4   cursor: (handle word)
+  5   next: (handle line)
+  6   prev: (handle line)
+  7 }
+  8 
+  9 # initialize line with a single empty word
+ 10 fn initialize-line _line: (addr line) {
+ 11   var line/esi: (addr line) <- copy _line
+ 12   var word-ah/eax: (addr handle word) <- get line, data
+ 13   allocate word-ah
+ 14   var cursor-ah/ecx: (addr handle word) <- get line, cursor
+ 15   copy-object word-ah, cursor-ah
+ 16   var word/eax: (addr word) <- lookup *word-ah
+ 17   initialize-word word
+ 18 }
+ 19 
+ 20 fn num-words-in-line _in: (addr line) -> _/eax: int {
+ 21   var in/esi: (addr line) <- copy _in
+ 22   var curr-ah/ecx: (addr handle word) <- get in, data
+ 23   var result/edi: int <- copy 0
+ 24   {
+ 25     var curr/eax: (addr word) <- lookup *curr-ah
+ 26     compare curr, 0
+ 27     break-if-=
+ 28     curr-ah <- get curr, next
+ 29     result <- increment
+ 30     loop
+ 31   }
+ 32   return result
+ 33 }
+ 34 
+ 35 fn line-list-length lines: (addr handle line) -> _/eax: int {
+ 36   var curr-ah/esi: (addr handle line) <- copy lines
+ 37   var result/edi: int <- copy 0
+ 38   {
+ 39     var curr/eax: (addr line) <- lookup *curr-ah
+ 40     compare curr, 0
+ 41     break-if-=
+ 42     curr-ah <- get curr, next
+ 43     result <- increment
+ 44     loop
+ 45   }
+ 46   return result
+ 47 }
+ 48 
+ 49 fn render-line screen: (addr screen), _line: (addr line), x: int, y: int, render-cursor?: boolean -> _/eax: int {
+ 50   var line/eax: (addr line) <- copy _line
+ 51   var first-word-ah/esi: (addr handle word) <- get line, data
+ 52   # cursor-word
+ 53   var cursor-word/edi: int <- copy 0
+ 54   compare render-cursor?, 0/false
+ 55   {
+ 56     break-if-=
+ 57     var cursor-word-ah/eax: (addr handle word) <- get line, cursor
+ 58     var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
+ 59     cursor-word <- copy _cursor-word
+ 60   }
+ 61   #
+ 62   var result/eax: int <- render-words screen, first-word-ah, x, y, cursor-word
+ 63   return result
+ 64 }
+ 65 
+ 66 fn parse-line in: (addr array byte), _out: (addr line) {
+ 67   var out/edi: (addr line) <- copy _out
+ 68   initialize-line out
+ 69   var dest/eax: (addr handle word) <- get out, data
+ 70   parse-words in, dest
+ 71 }
+ 72 
+ 73 #? fn main {
+ 74 #?   # line = [aaa, bbb, ccc, ddd]
+ 75 #?   var line-storage: line
+ 76 #?   var w-ah/eax: (addr handle word) <- get line-storage, data
+ 77 #?   allocate-word-with w-ah, "aaa"
+ 78 #?   append-word-at-end-with w-ah, "bbb"
+ 79 #?   append-word-at-end-with w-ah, "ccc"
+ 80 #?   append-word-at-end-with w-ah, "ddd"
+ 81 #?   var cursor-ah/ecx: (addr handle word) <- get line-storage, cursor
+ 82 #?   var w/eax: (addr word) <- lookup *w-ah
+ 83 #?   var next-ah/eax: (addr handle word) <- get w, next
+ 84 #?   copy-object next-ah, cursor-ah
+ 85 #?   var line-addr/eax: (addr line) <- address line-storage
+ 86 #?   var dummy/eax: int <- render-line 0/screen, line-addr, 0/x, 0/y, 1/render-cursor
+ 87 #? }
+ 88 
+ 89 fn render-line-with-stack screen: (addr screen), _line: (addr line), x: int, y: int, render-cursor?: boolean -> _/eax: int, _/ecx: int {
+ 90   var line/esi: (addr line) <- copy _line
+ 91   # cursor-word
+ 92   var cursor-word/edi: int <- copy 0
+ 93   compare render-cursor?, 0/false
+ 94   {
+ 95     break-if-=
+ 96     var cursor-word-ah/eax: (addr handle word) <- get line, cursor
+ 97     var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
+ 98     cursor-word <- copy _cursor-word
+ 99   }
+100   #
+101   var curr-word-ah/eax: (addr handle word) <- get line, data
+102   var _curr-word/eax: (addr word) <- lookup *curr-word-ah
+103   var curr-word/edx: (addr word) <- copy _curr-word
+104   var new-x/eax: int <- copy x  # increases each iteration
+105   var new-y/ebx: int <- copy y  # compute max across all iterations
+106   {
+107     compare curr-word, 0
+108     break-if-=
+109     var curr-y/ecx: int <- copy 0
+110     new-x, curr-y <- render-word-with-stack-and-cursor screen, line, curr-word, new-x, y, cursor-word
+111     compare curr-y, new-y
+112     {
+113       break-if-<=
+114       new-y <- copy curr-y
+115     }
+116     new-x <- add 1/inter-word-spacing
+117     # update
+118     var next-word-ah/eax: (addr handle word) <- get curr-word, next
+119     var next-word/eax: (addr word) <- lookup *next-word-ah
+120     curr-word <- copy next-word
+121     loop
+122   }
+123   return new-x, new-y
+124 }
+125 
+126 fn render-word-with-stack-and-cursor screen: (addr screen), line: (addr line), curr-word: (addr word), x: int, y: int, _cursor-word-addr: int -> _/eax: int, _/ecx: int {
+127   # print curr-word, with cursor if necessary
+128   var render-cursor?/eax: boolean <- copy 0/false
+129   var cursor-word-addr/ecx: int <- copy _cursor-word-addr
+130   {
+131     compare cursor-word-addr, curr-word
+132     break-if-!=
+133     render-cursor? <- copy 1/true
+134   }
+135   var new-x/eax: int <- render-word screen, curr-word, x, y, render-cursor?
+136   var new-x-saved/edx: int <- copy new-x
+137   add-to y, 2/word-stack-spacing
+138   # compute stack until word
+139   var stack-storage: value-stack
+140   var stack/edi: (addr value-stack) <- address stack-storage
+141   evaluate line, curr-word, stack
+142   # render stack
+143   var new-y/ecx: int <- copy 0
+144   new-x, new-y <- render-value-stack screen, stack, x, y
+145 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, new-x, 0xc/fg, 0/bg
+146 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, new-y, 3/fg, 0/bg
+147   compare new-x, new-x-saved
+148   {
+149     break-if->=
+150     new-x <- copy new-x-saved
+151   }
+152 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, new-x, 7/fg, 0/bg
+153   return new-x, new-y
+154 }
+155 
+156 fn test-render-line-with-stack-singleton {
+157   # line = [1]
+158   var line-storage: line
+159   var line/esi: (addr line) <- address line-storage
+160   parse-line "1", line
+161   # setup: screen
+162   var screen-on-stack: screen
+163   var screen/edi: (addr screen) <- address screen-on-stack
+164   initialize-screen screen, 0x20, 4
+165   #
+166   var new-x/eax: int <- copy 0
+167   var new-y/ecx: int <- copy 0
+168   new-x, new-y <- render-line-with-stack screen, line, 0/x, 0/y, 0/no-cursor
+169   check-screen-row screen, 0/y, "1  ", "F - test-render-line-with-stack-singleton/0"
+170   check-screen-row screen, 1/y, "   ", "F - test-render-line-with-stack-singleton/1"
+171   check-screen-row screen, 2/y, " 1 ", "F - test-render-line-with-stack-singleton/2"
+172   # not bothering to test hash colors for numbers
+173 }
+174 
+175 fn test-render-line-with-stack {
+176   # line = [1 2]
+177   var line-storage: line
+178   var line/esi: (addr line) <- address line-storage
+179   parse-line "1 2", line
+180   # setup: screen
+181   var screen-on-stack: screen
+182   var screen/edi: (addr screen) <- address screen-on-stack
+183   initialize-screen screen, 0x20, 4
+184   #
+185   var new-x/eax: int <- copy 0
+186   var new-y/ecx: int <- copy 0
+187   new-x, new-y <- render-line-with-stack screen, line, 0/x, 0/y, 0/no-cursor
+188   check-screen-row screen, 0/y, "1   2 ", "F - test-render-line-with-stack/0"
+189   check-screen-row screen, 1/y, "       ", "F - test-render-line-with-stack/1"
+190                                 #___ ___
+191   check-screen-row screen, 2/y, " 1   2 ", "F - test-render-line-with-stack/2"
+192   check-screen-row screen, 3/y, "     1 ", "F - test-render-line-with-stack/3"
+193   # not bothering to test hash colors for numbers
+194 }
+195 
+196 fn edit-line _self: (addr line), key: byte {
+197   var self/esi: (addr line) <- copy _self
+198   var cursor-word-ah/edx: (addr handle word) <- get self, cursor
+199   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
+200   var cursor-word/ecx: (addr word) <- copy _cursor-word
+201   compare key, 0x20/space
+202   $edit-line:space: {
+203     break-if-!=
+204     append-word cursor-word-ah
+205     var next-word-ah/eax: (addr handle word) <- get cursor-word, next
+206     copy-object next-word-ah, cursor-word-ah
+207     return
+208   }
+209   # otherwise insert key within current word
+210   var g/edx: grapheme <- copy key
+211   add-grapheme-to-word cursor-word, g
+212   # silently ignore other hotkeys
+213 }
+214 
+215 fn main {
+216   var line-storage: line
+217   var line/esi: (addr line) <- address line-storage
+218   initialize-line line
+219   $main:loop: {
+220     clear-screen 0/screen
+221     var dummy1/eax: int <- copy 0
+222     var dummy2/ecx: int <- copy 0
+223     dummy1, dummy2 <- render-line-with-stack 0/screen, line, 2/x, 2/y, 1/show-cursor
+224     {
+225       var key/eax: byte <- read-key 0/keyboard
+226       compare key, 0
+227       loop-if-=
+228       compare key, 0x71/q
+229       break-if-= $main:loop
+230       edit-line line, key
+231     }
+232     loop
+233   }
+234 }
+
+ + + diff --git a/html/baremetal/shell/value-stack.mu.html b/html/baremetal/shell/value-stack.mu.html new file mode 100644 index 00000000..1174da46 --- /dev/null +++ b/html/baremetal/shell/value-stack.mu.html @@ -0,0 +1,304 @@ + + + + +Mu - baremetal/shell/value-stack.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/value-stack.mu +
+  1 # value stacks encode the result of a program at a single point in time
+  2 # they are typically rendered vertically
+  3 
+  4 type value-stack {
+  5   data: (handle array value)
+  6   top: int
+  7 }
+  8 
+  9 fn initialize-value-stack _self: (addr value-stack), n: int {
+ 10   var self/esi: (addr value-stack) <- copy _self
+ 11   var d/edi: (addr handle array value) <- get self, data
+ 12   populate d, n
+ 13   var top/eax: (addr int) <- get self, top
+ 14   copy-to *top, 0
+ 15 }
+ 16 
+ 17 fn clear-value-stack _self: (addr value-stack) {
+ 18   var self/esi: (addr value-stack) <- copy _self
+ 19   var top/eax: (addr int) <- get self, top
+ 20   copy-to *top, 0
+ 21 }
+ 22 
+ 23 fn push-number-to-value-stack _self: (addr value-stack), _val: float {
+ 24   var self/esi: (addr value-stack) <- copy _self
+ 25   var top-addr/ecx: (addr int) <- get self, top
+ 26   var data-ah/edx: (addr handle array value) <- get self, data
+ 27   var data/eax: (addr array value) <- lookup *data-ah
+ 28   var top/edx: int <- copy *top-addr
+ 29   var dest-offset/edx: (offset value) <- compute-offset data, top
+ 30   var dest-addr/edx: (addr value) <- index data, dest-offset
+ 31   var dest-addr2/eax: (addr float) <- get dest-addr, number-data
+ 32   var val/xmm0: float <- copy _val
+ 33   copy-to *dest-addr2, val
+ 34   increment *top-addr
+ 35   var type-addr/eax: (addr int) <- get dest-addr, type
+ 36   copy-to *type-addr, 0/number
+ 37 }
+ 38 
+ 39 fn push-int-to-value-stack _self: (addr value-stack), _val: int {
+ 40   var self/esi: (addr value-stack) <- copy _self
+ 41   var top-addr/ecx: (addr int) <- get self, top
+ 42   var data-ah/edx: (addr handle array value) <- get self, data
+ 43   var data/eax: (addr array value) <- lookup *data-ah
+ 44   var top/edx: int <- copy *top-addr
+ 45   var dest-offset/edx: (offset value) <- compute-offset data, top
+ 46   var dest-addr/edx: (addr value) <- index data, dest-offset
+ 47   var dest-addr2/eax: (addr float) <- get dest-addr, number-data
+ 48   var val/xmm0: float <- convert _val
+ 49   copy-to *dest-addr2, val
+ 50   increment *top-addr
+ 51   var type-addr/eax: (addr int) <- get dest-addr, type
+ 52   copy-to *type-addr, 0/number
+ 53 }
+ 54 
+ 55 fn push-string-to-value-stack _self: (addr value-stack), val: (handle array byte) {
+ 56   var self/esi: (addr value-stack) <- copy _self
+ 57   var top-addr/ecx: (addr int) <- get self, top
+ 58   var data-ah/edx: (addr handle array value) <- get self, data
+ 59   var data/eax: (addr array value) <- lookup *data-ah
+ 60   var top/edx: int <- copy *top-addr
+ 61   var dest-offset/edx: (offset value) <- compute-offset data, top
+ 62   var dest-addr/edx: (addr value) <- index data, dest-offset
+ 63   var dest-addr2/eax: (addr handle array byte) <- get dest-addr, text-data
+ 64   copy-handle val, dest-addr2
+ 65   var dest-addr3/eax: (addr int) <- get dest-addr, type
+ 66   copy-to *dest-addr3, 1/string
+ 67   increment *top-addr
+ 68 }
+ 69 
+ 70 fn push-array-to-value-stack _self: (addr value-stack), val: (handle array value) {
+ 71   var self/esi: (addr value-stack) <- copy _self
+ 72   var top-addr/ecx: (addr int) <- get self, top
+ 73   var data-ah/edx: (addr handle array value) <- get self, data
+ 74   var data/eax: (addr array value) <- lookup *data-ah
+ 75   var top/edx: int <- copy *top-addr
+ 76   var dest-offset/edx: (offset value) <- compute-offset data, top
+ 77   var dest-addr/edx: (addr value) <- index data, dest-offset
+ 78   var dest-addr2/eax: (addr handle array value) <- get dest-addr, array-data
+ 79   copy-handle val, dest-addr2
+ 80   # update type
+ 81   var dest-addr3/eax: (addr int) <- get dest-addr, type
+ 82   copy-to *dest-addr3, 2/array
+ 83   increment *top-addr
+ 84 }
+ 85 
+ 86 fn push-boolean-to-value-stack _self: (addr value-stack), _val: boolean {
+ 87   var self/esi: (addr value-stack) <- copy _self
+ 88   var top-addr/ecx: (addr int) <- get self, top
+ 89   var data-ah/edx: (addr handle array value) <- get self, data
+ 90   var data/eax: (addr array value) <- lookup *data-ah
+ 91   var top/edx: int <- copy *top-addr
+ 92   var dest-offset/edx: (offset value) <- compute-offset data, top
+ 93   var dest-addr/edx: (addr value) <- index data, dest-offset
+ 94   var dest-addr2/eax: (addr boolean) <- get dest-addr, boolean-data
+ 95   var val/esi: boolean <- copy _val
+ 96   copy-to *dest-addr2, val
+ 97   increment *top-addr
+ 98   var type-addr/eax: (addr int) <- get dest-addr, type
+ 99   copy-to *type-addr, 3/boolean
+100 }
+101 
+102 fn push-value-stack _self: (addr value-stack), val: (addr value) {
+103   var self/esi: (addr value-stack) <- copy _self
+104   var top-addr/ecx: (addr int) <- get self, top
+105   var data-ah/edx: (addr handle array value) <- get self, data
+106   var data/eax: (addr array value) <- lookup *data-ah
+107   var top/edx: int <- copy *top-addr
+108   var dest-offset/edx: (offset value) <- compute-offset data, top
+109   var dest-addr/edx: (addr value) <- index data, dest-offset
+110   copy-object val, dest-addr
+111   increment *top-addr
+112 }
+113 
+114 fn pop-number-from-value-stack _self: (addr value-stack) -> _/xmm0: float {
+115   var self/esi: (addr value-stack) <- copy _self
+116   var top-addr/ecx: (addr int) <- get self, top
+117   {
+118     compare *top-addr, 0
+119     break-if->
+120     abort "pop number: empty stack"
+121   }
+122   decrement *top-addr
+123   var data-ah/edx: (addr handle array value) <- get self, data
+124   var data/eax: (addr array value) <- lookup *data-ah
+125   var top/edx: int <- copy *top-addr
+126   var dest-offset/edx: (offset value) <- compute-offset data, top
+127   var result-addr/eax: (addr value) <- index data, dest-offset
+128   var result-addr2/eax: (addr float) <- get result-addr, number-data
+129   return *result-addr2
+130 }
+131 
+132 fn pop-boolean-from-value-stack _self: (addr value-stack) -> _/eax: boolean {
+133   var self/esi: (addr value-stack) <- copy _self
+134   var top-addr/ecx: (addr int) <- get self, top
+135   {
+136     compare *top-addr, 0
+137     break-if->
+138     abort "pop boolean: empty stack"
+139   }
+140   decrement *top-addr
+141   var data-ah/edx: (addr handle array value) <- get self, data
+142   var data/eax: (addr array value) <- lookup *data-ah
+143   var top/edx: int <- copy *top-addr
+144   var dest-offset/edx: (offset value) <- compute-offset data, top
+145   var result-addr/eax: (addr value) <- index data, dest-offset
+146   var result-addr2/eax: (addr boolean) <- get result-addr, boolean-data
+147   return *result-addr2
+148 }
+149 
+150 fn value-stack-empty? _self: (addr value-stack) -> _/eax: boolean {
+151   var self/esi: (addr value-stack) <- copy _self
+152   var top/eax: (addr int) <- get self, top
+153   compare *top, 0
+154   {
+155     break-if-!=
+156     return 1/true
+157   }
+158   return 0/false
+159 }
+160 
+161 fn value-stack-length _self: (addr value-stack) -> _/eax: int {
+162   var self/esi: (addr value-stack) <- copy _self
+163   var top-addr/eax: (addr int) <- get self, top
+164   return *top-addr
+165 }
+166 
+167 fn test-boolean {
+168   var stack-storage: value-stack
+169   var stack/esi: (addr value-stack) <- address stack-storage
+170   push-boolean-to-value-stack stack, 0/false
+171   var result/eax: boolean <- pop-boolean-from-value-stack stack
+172   check-not result, "F - test-boolean/false"
+173   push-boolean-to-value-stack stack, 1/true
+174   var result/eax: boolean <- pop-boolean-from-value-stack stack
+175   check result, "F - test-boolean/true"
+176 }
+177 
+178 fn dump-stack _self: (addr value-stack) {
+179   var self/esi: (addr value-stack) <- copy _self
+180   var data-ah/eax: (addr handle array value) <- get self, data
+181   var _data/eax: (addr array value) <- lookup *data-ah
+182   var data/edi: (addr array value) <- copy _data
+183   var top-addr/ecx: (addr int) <- get self, top
+184   var top/ecx: int <- copy *top-addr
+185   top <- decrement
+186   var y/edx: int <- copy 0xa
+187   var dummy/eax: int <- draw-text-rightward-over-full-screen 0/screen, "==", 0/x, 9/y, 0xc/red, 0/bg
+188   {
+189     compare top, 0
+190     break-if-<
+191     var dest-offset/eax: (offset value) <- compute-offset data, top
+192     var curr/eax: (addr value) <- index data, dest-offset
+193     var dummy/eax: int <- render-value 0/screen, curr, 0/x, y, 0/no-color
+194     top <- decrement
+195     y <- increment
+196     loop
+197   }
+198 }
+199 
+200 fn render-value-stack screen: (addr screen), _self: (addr value-stack), x: int, y: int -> _/eax: int, _/ecx: int {
+201   var self/ecx: (addr value-stack) <- copy _self
+202   var data-ah/eax: (addr handle array value) <- get self, data
+203   var _data/eax: (addr array value) <- lookup *data-ah
+204   var data/edi: (addr array value) <- copy _data
+205   var top-addr/eax: (addr int) <- get self, top
+206   var curr-idx/ecx: int <- copy *top-addr
+207   curr-idx <- decrement
+208   var new-x/edx: int <- copy 0
+209   {
+210     compare curr-idx, 0
+211     break-if-<
+212     var dest-offset/eax: (offset value) <- compute-offset data, curr-idx
+213     var curr/eax: (addr value) <- index data, dest-offset
+214     var curr-x/eax: int <- render-value screen, curr, x, y, 1/top-level
+215     {
+216       compare curr-x, new-x
+217       break-if-<=
+218       new-x <- copy curr-x
+219     }
+220     curr-idx <- decrement
+221     increment y
+222     loop
+223   }
+224   return new-x, y
+225 }
+226 
+227 fn test-render-value-stack {
+228   var stack-storage: value-stack
+229   var stack/esi: (addr value-stack) <- address stack-storage
+230   push-int-to-value-stack stack, 3
+231   # setup: screen
+232   var screen-on-stack: screen
+233   var screen/edi: (addr screen) <- address screen-on-stack
+234   initialize-screen screen, 0x20, 4
+235   #
+236   var final-x/eax: int <- copy 0
+237   var final-y/ecx: int <- copy 0
+238   final-x, final-y <- render-value-stack screen, stack, 0/x, 0/y
+239   check-ints-equal final-y, 1, "F - test-render-value-stack y"
+240   check-ints-equal final-x, 3, "F - test-render-value-stack x"
+241 }
+
+ + + diff --git a/html/baremetal/shell/value.mu.html b/html/baremetal/shell/value.mu.html new file mode 100644 index 00000000..33050043 --- /dev/null +++ b/html/baremetal/shell/value.mu.html @@ -0,0 +1,326 @@ + + + + +Mu - baremetal/shell/value.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/value.mu +
+  1 # todo: turn this into a sum type
+  2 type value {
+  3   type: int
+  4   number-data: float  # if type = 0
+  5   text-data: (handle array byte)  # if type = 1
+  6   array-data: (handle array value)  # if type = 2
+  7   boolean-data: boolean  # if type = 3
+  8 }
+  9 
+ 10 # top-level? is a hack just for numbers
+ 11 # we'll eventually need to return a y coordinate as well to render 2D values
+ 12 fn render-value screen: (addr screen), _val: (addr value), x: int, y: int, top-level?: boolean -> _/eax: int {
+ 13   var val/esi: (addr value) <- copy _val
+ 14   var val-type/ecx: (addr int) <- get val, type
+ 15   compare *val-type, 1/string
+ 16   {
+ 17     break-if-!=
+ 18     var val-ah/eax: (addr handle array byte) <- get val, text-data
+ 19     var _val-string/eax: (addr array byte) <- lookup *val-ah
+ 20     var val-string/ecx: (addr array byte) <- copy _val-string
+ 21     var new-x/eax: int <- render-string screen, val-string, x, y
+ 22     return new-x
+ 23   }
+ 24   compare *val-type, 2/array
+ 25   {
+ 26     break-if-!=
+ 27     var val-ah/eax: (addr handle array value) <- get val, array-data
+ 28     var _val-array/eax: (addr array value) <- lookup *val-ah
+ 29     var val-array/edx: (addr array value) <- copy _val-array
+ 30     var new-x/eax: int <- render-array screen, val-array, x, y
+ 31     return new-x
+ 32   }
+ 33   compare *val-type, 3/boolean
+ 34   {
+ 35     break-if-!=
+ 36     var val/eax: (addr boolean) <- get val, boolean-data
+ 37     var new-x/eax: int <- render-boolean screen, *val, x, y
+ 38     return new-x
+ 39   }
+ 40   # render ints by default for now
+ 41   var val-num/eax: (addr float) <- get val, number-data
+ 42   var new-x/eax: int <- render-number screen, *val-num, x, y, top-level?
+ 43   return new-x
+ 44 }
+ 45 
+ 46 fn initialize-value-with-integer _self: (addr value), n: int {
+ 47   var self/esi: (addr value) <- copy _self
+ 48   var type/eax: (addr int) <- get self, type
+ 49   copy-to *type, 0/number
+ 50   var val/xmm0: float <- convert n
+ 51   var dest/eax: (addr float) <- get self, number-data
+ 52   copy-to *dest, val
+ 53 }
+ 54 
+ 55 fn initialize-value-with-float _self: (addr value), n: float {
+ 56   var self/esi: (addr value) <- copy _self
+ 57   var type/eax: (addr int) <- get self, type
+ 58   copy-to *type, 0/number
+ 59   var val/xmm0: float <- copy n
+ 60   var dest/eax: (addr float) <- get self, number-data
+ 61   copy-to *dest, val
+ 62 }
+ 63 
+ 64 # synaesthesia
+ 65 # TODO: right-justify
+ 66 fn render-number screen: (addr screen), val: float, x: int, y: int, top-level?: boolean -> _/eax: int {
+ 67   # if we're inside an array, don't color
+ 68   compare top-level?, 0
+ 69   {
+ 70     break-if-!=
+ 71     var new-x/eax: int <- render-float-decimal screen, val, 3/precision, x, y, 3/fg, 0/bg
+ 72     return new-x
+ 73   }
+ 74   var val-int/eax: int <- convert val
+ 75   var _bg/eax: int <- hash-color val-int
+ 76   var bg/ecx: int <- copy _bg
+ 77   var fg/edx: int <- copy 7
+ 78   {
+ 79     compare bg, 2
+ 80     break-if-!=
+ 81     fg <- copy 0
+ 82   }
+ 83   {
+ 84     compare bg, 3
+ 85     break-if-!=
+ 86     fg <- copy 0
+ 87   }
+ 88   {
+ 89     compare bg, 6
+ 90     break-if-!=
+ 91     fg <- copy 0
+ 92   }
+ 93   draw-code-point screen, 0x20/space, x, y, fg, bg
+ 94   increment x
+ 95   var new-x/eax: int <- render-float-decimal screen, val, 3/precision, x, y, fg, bg
+ 96   draw-code-point screen, 0x20/space, new-x, y, fg, bg
+ 97   new-x <- increment
+ 98   return new-x
+ 99 }
+100 
+101 fn hash-color val: int -> _/eax: int {
+102   var quotient/eax: int <- copy 0
+103   var remainder/edx: int <- copy 0
+104   quotient, remainder <- integer-divide val, 7  # assumes that 7 is always the background color
+105   return remainder
+106 }
+107 
+108 fn test-render-number {
+109   # setup: screen
+110   var screen-on-stack: screen
+111   var screen/edi: (addr screen) <- address screen-on-stack
+112   initialize-screen screen, 0x20, 4
+113   # integers render with some padding spaces
+114   var new-x/eax: int <- render-number screen, 0/n, 0/x, 0/y, 1/at-top-level
+115   check-screen-row screen, 0/y, " 0 ", "F - test-render-number"
+116   check-ints-equal new-x, 3, "F - test-render-number: result"
+117   # we won't bother testing the background colors; lots of flexibility there
+118 }
+119 
+120 fn initialize-value-with-string _self: (addr value), s: (addr array byte) {
+121   var self/esi: (addr value) <- copy _self
+122   var type/eax: (addr int) <- get self, type
+123   copy-to *type, 1/string
+124   var dest/eax: (addr handle array byte) <- get self, text-data
+125   copy-array-object s, dest
+126 }
+127 
+128 fn render-string screen: (addr screen), _val: (addr array byte), x: int, y: int -> _/eax: int {
+129   var val/esi: (addr array byte) <- copy _val
+130   compare val, 0
+131   {
+132     break-if-!=
+133     return x
+134   }
+135   var orig-len/ecx: int <- length val
+136   # truncate to 12 graphemes
+137   # TODO: more sophisticated interactive rendering
+138   var truncated: (handle array byte)
+139   var truncated-ah/eax: (addr handle array byte) <- address truncated
+140   substring val, 0, 0xc, truncated-ah
+141   var _truncated-string/eax: (addr array byte) <- lookup *truncated-ah
+142   var truncated-string/edx: (addr array byte) <- copy _truncated-string
+143   var len/ebx: int <- length truncated-string
+144   draw-code-point screen, 0x22/double-quote, x, y, 7/fg, 0/bg
+145   increment x
+146   var new-x/eax: int <- draw-text-rightward-over-full-screen screen, truncated-string, x, y, 7/fg, 0/bg
+147   compare len, orig-len
+148   {
+149     break-if-=
+150     new-x <- draw-text-rightward-over-full-screen screen, "...", new-x, y, 7/fg, 0/bg
+151   }
+152   draw-code-point screen, 0x22/double-quote, new-x, y, 7/fg, 0/bg
+153   new-x <- increment
+154   return new-x
+155 }
+156 
+157 fn test-render-string {
+158   # setup: screen
+159   var screen-on-stack: screen
+160   var screen/edi: (addr screen) <- address screen-on-stack
+161   initialize-screen screen, 0x20, 4
+162   # strings render with quotes
+163   var new-x/eax: int <- render-string screen, "abc", 0/x, 0/y
+164   check-screen-row screen, 0/y, "\"abc\"", "F - test-render-string"
+165   check-ints-equal new-x, 5, "F - test-render-string: result"
+166 }
+167 
+168 fn initialize-value-with-array-of-integers _self: (addr value), s: (addr array byte) {
+169   # parse s into a temporary array of ints
+170   var tmp-storage: (handle array int)
+171   var tmp-ah/eax: (addr handle array int) <- address tmp-storage
+172   parse-array-of-decimal-ints s, tmp-ah  # leak
+173   var _tmp/eax: (addr array int ) <- lookup *tmp-ah
+174   var tmp/esi: (addr array int ) <- copy _tmp
+175   # load the array into values
+176   var self/edi: (addr value) <- copy _self
+177   var type/eax: (addr int) <- get self, type
+178   copy-to *type, 2/string
+179   var dest-array-ah/eax: (addr handle array value) <- get self, array-data
+180   var len/ebx: int <- length tmp
+181   populate dest-array-ah, len
+182   var _dest-array/eax: (addr array value) <- lookup *dest-array-ah
+183   var dest-array/edi: (addr array value) <- copy _dest-array
+184   var i/eax: int <- copy 0
+185   {
+186     compare i, len
+187     break-if->=
+188     var src-addr/ecx: (addr int) <- index tmp, i
+189     var src/ecx: int <- copy *src-addr
+190     var src-f/xmm0: float <- convert src
+191     var dest-offset/edx: (offset value) <- compute-offset dest-array, i
+192     var dest-val/edx: (addr value) <- index dest-array, dest-offset
+193     var dest/edx: (addr float) <- get dest-val, number-data
+194     copy-to *dest, src-f
+195     i <- increment
+196     loop
+197   }
+198 }
+199 
+200 fn render-array screen: (addr screen), _arr: (addr array value), x: int, y: int -> _/eax: int {
+201   # don't surround in spaces
+202   draw-code-point screen, 0x5b/open-bracket, x, y, 7/fg, 0/bg
+203   increment x
+204   var arr/esi: (addr array value) <- copy _arr
+205   var max/ecx: int <- length arr
+206   var i/edx: int <- copy 0
+207   var new-x/eax: int <- copy x
+208   {
+209     compare i, max
+210     break-if->=
+211     {
+212       compare i, 0
+213       break-if-=
+214       draw-code-point screen, 0x20/space, new-x, y, 7/fg, 0/bg
+215       new-x <- increment
+216     }
+217     var off/ecx: (offset value) <- compute-offset arr, i
+218     var x/ecx: (addr value) <- index arr, off
+219     new-x <- render-value screen, x, new-x, y, 0/nested
+220     i <- increment
+221     loop
+222   }
+223   draw-code-point screen, 0x5d/close-bracket, new-x, y, 7/fg, 0/bg
+224   new-x <- increment
+225   return new-x
+226 }
+227 
+228 fn test-render-array {
+229   # setup: screen
+230   var screen-on-stack: screen
+231   var screen/edi: (addr screen) <- address screen-on-stack
+232   initialize-screen screen, 0x20, 4
+233   #
+234   var val-storage: value
+235   var val/eax: (addr value) <- address val-storage
+236   initialize-value-with-array-of-integers val, "0 1 2"
+237   var val-array-ah/eax: (addr handle array value) <- get val, array-data
+238   var val-array/eax: (addr array value) <- lookup *val-array-ah
+239   var new-x/eax: int <- render-array screen, val-array, 0/x, 0/y
+240   check-screen-row screen, 0/y, "[0 1 2]", "F - test-render-array"
+241   check-ints-equal new-x, 7, "F - test-render-array: result"
+242 }
+243 
+244 fn initialize-value-with-boolean _self: (addr value), _b: boolean {
+245   var self/esi: (addr value) <- copy _self
+246   var type/eax: (addr int) <- get self, type
+247   copy-to *type, 3/boolean
+248   var dest/edi: (addr boolean) <- get self, boolean-data
+249   var b/esi: boolean <- copy _b
+250   copy-to *dest, b
+251 }
+252 
+253 fn render-boolean screen: (addr screen), val: boolean, x: int, y: int -> _/eax: int {
+254   var new-x/eax: int <- copy 0
+255   compare val, 0/false
+256   {
+257     break-if-=
+258     new-x <- draw-text-rightward-over-full-screen screen, "true", new-x, y, 7/fg, 0/bg
+259     return new-x
+260   }
+261   new-x <- draw-text-rightward-over-full-screen screen, "false", new-x, y, 7/fg, 0/bg
+262   return new-x
+263 }
+
+ + + diff --git a/html/baremetal/shell/word.mu.html b/html/baremetal/shell/word.mu.html new file mode 100644 index 00000000..40412697 --- /dev/null +++ b/html/baremetal/shell/word.mu.html @@ -0,0 +1,784 @@ + + + + +Mu - baremetal/shell/word.mu + + + + + + + + + + +https://github.com/akkartik/mu/blob/main/baremetal/shell/word.mu +
+  1 type word {
+  2   scalar-data: (handle gap-buffer)
+  3   next: (handle word)
+  4   prev: (handle word)
+  5 }
+  6 
+  7 fn initialize-word _self: (addr word) {
+  8   var self/esi: (addr word) <- copy _self
+  9   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+ 10   allocate data-ah
+ 11   var data/eax: (addr gap-buffer) <- lookup *data-ah
+ 12   initialize-gap-buffer data
+ 13 }
+ 14 
+ 15 ## some helpers for creating words. mostly for tests
+ 16 
+ 17 fn initialize-word-with _self: (addr word), s: (addr array byte) {
+ 18   var self/esi: (addr word) <- copy _self
+ 19   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+ 20   allocate data-ah
+ 21   var data/eax: (addr gap-buffer) <- lookup *data-ah
+ 22   initialize-gap-buffer-with data, s
+ 23 }
+ 24 
+ 25 fn allocate-word-with _out: (addr handle word), s: (addr array byte) {
+ 26   var out/eax: (addr handle word) <- copy _out
+ 27   allocate out
+ 28   var out-addr/eax: (addr word) <- lookup *out
+ 29   initialize-word-with out-addr, s
+ 30 }
+ 31 
+ 32 # just for tests for now
+ 33 # TODO: handle existing next
+ 34 # one implication of handles: append must take a handle
+ 35 fn append-word-with self-h: (handle word), s: (addr array byte) {
+ 36   var self/eax: (addr word) <- lookup self-h
+ 37   var next-ah/eax: (addr handle word) <- get self, next
+ 38   allocate-word-with next-ah, s
+ 39   var next/eax: (addr word) <- lookup *next-ah
+ 40   var prev-ah/eax: (addr handle word) <- get next, prev
+ 41   copy-handle self-h, prev-ah
+ 42 }
+ 43 
+ 44 # just for tests for now
+ 45 # TODO: handle existing prev
+ 46 fn prepend-word-with self-h: (handle word), s: (addr array byte) {
+ 47   var self/eax: (addr word) <- lookup self-h
+ 48   var prev-ah/eax: (addr handle word) <- get self, prev
+ 49   allocate-word-with prev-ah, s
+ 50   var prev/eax: (addr word) <- lookup *prev-ah
+ 51   var next-ah/eax: (addr handle word) <- get prev, next
+ 52   copy-handle self-h, next-ah
+ 53 }
+ 54 
+ 55 ## real primitives
+ 56 
+ 57 fn move-word-contents _src-ah: (addr handle word), _dest-ah: (addr handle word) {
+ 58   var dest-ah/eax: (addr handle word) <- copy _dest-ah
+ 59   var _dest/eax: (addr word) <- lookup *dest-ah
+ 60   var dest/edi: (addr word) <- copy _dest
+ 61   var src-ah/eax: (addr handle word) <- copy _src-ah
+ 62   var _src/eax: (addr word) <- lookup *src-ah
+ 63   var src/esi: (addr word) <- copy _src
+ 64   cursor-to-start src
+ 65   var src-data-ah/eax: (addr handle gap-buffer) <- get src, scalar-data
+ 66   var src-data/eax: (addr gap-buffer) <- lookup *src-data-ah
+ 67   var src-stack/ecx: (addr grapheme-stack) <- get src-data, right
+ 68   {
+ 69     var done?/eax: boolean <- grapheme-stack-empty? src-stack
+ 70     compare done?, 0/false
+ 71     break-if-!=
+ 72     var g/eax: grapheme <- pop-grapheme-stack src-stack
+ 73     add-grapheme-to-word dest, g
+ 74     loop
+ 75   }
+ 76 }
+ 77 
+ 78 fn copy-word-contents-before-cursor _src-ah: (addr handle word), _dest-ah: (addr handle word) {
+ 79   var dest-ah/eax: (addr handle word) <- copy _dest-ah
+ 80   var _dest/eax: (addr word) <- lookup *dest-ah
+ 81   var dest/edi: (addr word) <- copy _dest
+ 82   var src-ah/eax: (addr handle word) <- copy _src-ah
+ 83   var src/eax: (addr word) <- lookup *src-ah
+ 84   var src-data-ah/eax: (addr handle gap-buffer) <- get src, scalar-data
+ 85   var src-data/eax: (addr gap-buffer) <- lookup *src-data-ah
+ 86   var src-stack/ecx: (addr grapheme-stack) <- get src-data, left
+ 87   var src-stack-data-ah/eax: (addr handle array grapheme) <- get src-stack, data
+ 88   var _src-stack-data/eax: (addr array grapheme) <- lookup *src-stack-data-ah
+ 89   var src-stack-data/edx: (addr array grapheme) <- copy _src-stack-data
+ 90   var top-addr/ecx: (addr int) <- get src-stack, top
+ 91   var i/eax: int <- copy 0
+ 92   {
+ 93     compare i, *top-addr
+ 94     break-if->=
+ 95     var g/edx: (addr grapheme) <- index src-stack-data, i
+ 96     add-grapheme-to-word dest, *g
+ 97     i <- increment
+ 98     loop
+ 99   }
+100 }
+101 
+102 fn word-equal? _self: (addr word), s: (addr array byte) -> _/eax: boolean {
+103   var self/esi: (addr word) <- copy _self
+104   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+105   var data/eax: (addr gap-buffer) <- lookup *data-ah
+106   var result/eax: boolean <- gap-buffer-equal? data, s
+107   return result
+108 }
+109 
+110 fn words-equal? _self: (addr word), _w: (addr word) -> _/eax: boolean {
+111   var self/eax: (addr word) <- copy _self
+112   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+113   var _data/eax: (addr gap-buffer) <- lookup *data-ah
+114   var data/ecx: (addr gap-buffer) <- copy _data
+115   var w/eax: (addr word) <- copy _w
+116   var w-data-ah/eax: (addr handle gap-buffer) <- get w, scalar-data
+117   var w-data/eax: (addr gap-buffer) <- lookup *w-data-ah
+118   var result/eax: boolean <- gap-buffers-equal? data, w-data
+119   return result
+120 }
+121 
+122 fn word-length _self: (addr word) -> _/eax: int {
+123   var self/esi: (addr word) <- copy _self
+124   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+125   var data/eax: (addr gap-buffer) <- lookup *data-ah
+126   var result/eax: int <- gap-buffer-length data
+127   return result
+128 }
+129 
+130 fn first-word _in: (addr handle word), out: (addr handle word) {
+131   var curr-ah/esi: (addr handle word) <- copy _in
+132   var curr/eax: (addr word) <- lookup *curr-ah
+133   var prev/edi: (addr handle word) <- copy 0
+134   {
+135     prev <- get curr, prev
+136     var curr/eax: (addr word) <- lookup *prev
+137     compare curr, 0
+138     break-if-=
+139     copy-object prev, curr-ah
+140     loop
+141   }
+142   copy-object curr-ah, out
+143 }
+144 
+145 fn final-word _in: (addr handle word), out: (addr handle word) {
+146   var curr-h: (handle word)
+147   var curr-ah/esi: (addr handle word) <- address curr-h
+148   copy-object _in, curr-ah
+149   var curr/eax: (addr word) <- copy 0
+150   var next/edi: (addr handle word) <- copy 0
+151   {
+152     curr <- lookup *curr-ah
+153     next <- get curr, next
+154     curr <- lookup *next
+155     compare curr, 0
+156     break-if-=
+157     copy-object next, curr-ah
+158     loop
+159   }
+160   copy-object curr-ah, out  # modify 'out' right at the end, just in case it's same as 'in'
+161 }
+162 
+163 fn first-grapheme _self: (addr word) -> _/eax: grapheme {
+164   var self/esi: (addr word) <- copy _self
+165   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+166   var data/eax: (addr gap-buffer) <- lookup *data-ah
+167   var result/eax: grapheme <- first-grapheme-in-gap-buffer data
+168   return result
+169 }
+170 
+171 fn grapheme-before-cursor _self: (addr word) -> _/eax: grapheme {
+172   var self/esi: (addr word) <- copy _self
+173   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+174   var data/eax: (addr gap-buffer) <- lookup *data-ah
+175   var result/eax: grapheme <- grapheme-before-cursor-in-gap-buffer data
+176   return result
+177 }
+178 
+179 fn add-grapheme-to-word _self: (addr word), c: grapheme {
+180   var self/esi: (addr word) <- copy _self
+181   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+182   var data/eax: (addr gap-buffer) <- lookup *data-ah
+183   add-grapheme-at-gap data, c
+184 }
+185 
+186 fn cursor-at-start? _self: (addr word) -> _/eax: boolean {
+187   var self/esi: (addr word) <- copy _self
+188   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+189   var data/eax: (addr gap-buffer) <- lookup *data-ah
+190   var result/eax: boolean <- gap-at-start? data
+191   return result
+192 }
+193 
+194 fn cursor-at-end? _self: (addr word) -> _/eax: boolean {
+195   var self/esi: (addr word) <- copy _self
+196   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+197   var data/eax: (addr gap-buffer) <- lookup *data-ah
+198   var result/eax: boolean <- gap-at-end? data
+199   return result
+200 }
+201 
+202 fn cursor-left _self: (addr word) {
+203   var self/esi: (addr word) <- copy _self
+204   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+205   var data/eax: (addr gap-buffer) <- lookup *data-ah
+206   var dummy/eax: grapheme <- gap-left data
+207 }
+208 
+209 fn cursor-right _self: (addr word) {
+210   var self/esi: (addr word) <- copy _self
+211   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+212   var data/eax: (addr gap-buffer) <- lookup *data-ah
+213   var dummy/eax: grapheme <- gap-right data
+214 }
+215 
+216 fn cursor-to-start _self: (addr word) {
+217   var self/esi: (addr word) <- copy _self
+218   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+219   var data/eax: (addr gap-buffer) <- lookup *data-ah
+220   gap-to-start data
+221 }
+222 
+223 fn cursor-to-end _self: (addr word) {
+224   var self/esi: (addr word) <- copy _self
+225   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+226   var data/eax: (addr gap-buffer) <- lookup *data-ah
+227   gap-to-end data
+228 }
+229 
+230 fn cursor-index _self: (addr word) -> _/eax: int {
+231   var self/esi: (addr word) <- copy _self
+232   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+233   var data/eax: (addr gap-buffer) <- lookup *data-ah
+234   var result/eax: int <- index-of-gap data
+235   return result
+236 }
+237 
+238 fn delete-before-cursor _self: (addr word) {
+239   var self/esi: (addr word) <- copy _self
+240   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+241   var data/eax: (addr gap-buffer) <- lookup *data-ah
+242   delete-before-gap data
+243 }
+244 
+245 fn pop-after-cursor _self: (addr word) -> _/eax: grapheme {
+246   var self/esi: (addr word) <- copy _self
+247   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+248   var data/eax: (addr gap-buffer) <- lookup *data-ah
+249   var result/eax: grapheme <- pop-after-gap data
+250   return result
+251 }
+252 
+253 fn delete-next _self: (addr word) {
+254   var self/esi: (addr word) <- copy _self
+255   var next-ah/edi: (addr handle word) <- get self, next
+256   var next/eax: (addr word) <- lookup *next-ah
+257   compare next, 0
+258   break-if-=
+259   var next-next-ah/ecx: (addr handle word) <- get next, next
+260   var self-ah/esi: (addr handle word) <- get next, prev
+261   copy-object next-next-ah, next-ah
+262   var new-next/eax: (addr word) <- lookup *next-next-ah
+263   compare new-next, 0
+264   break-if-=
+265   var dest/eax: (addr handle word) <- get new-next, prev
+266   copy-object self-ah, dest
+267 }
+268 
+269 fn render-word screen: (addr screen), _self: (addr word), x: int, y: int, render-cursor?: boolean -> _/eax: int {
+270   var self/esi: (addr word) <- copy _self
+271   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+272   var data/eax: (addr gap-buffer) <- lookup *data-ah
+273   var result/eax: int <- render-gap-buffer screen, data, x, y, render-cursor?
+274   return result
+275 }
+276 
+277 fn render-words screen: (addr screen), _words-ah: (addr handle word), x: int, y: int, cursor-word-addr: int -> _/eax: int {
+278   var words-ah/eax: (addr handle word) <- copy _words-ah
+279   var _words-a/eax: (addr word) <- lookup *words-ah
+280   var words-a/ecx: (addr word) <- copy _words-a
+281   compare words-a, 0
+282   {
+283     break-if-!=
+284     return x
+285   }
+286   # print
+287   var render-cursor?/edx: boolean <- copy 0/false
+288   {
+289     compare cursor-word-addr, words-a
+290     break-if-!=
+291     render-cursor? <- copy 1/true
+292   }
+293   var next-x/eax: int <- render-word screen, words-a, x, y, render-cursor?
+294   var space/edx: grapheme <- copy 0x20/space
+295   draw-grapheme screen, space, next-x, y, 3/fg=cyan, 0/bg
+296   next-x <- increment
+297   # recurse
+298   var next-ah/ecx: (addr handle word) <- get words-a, next
+299   next-x <- render-words screen, next-ah, next-x, y, cursor-word-addr
+300   return next-x
+301 }
+302 
+303 fn test-render-words {
+304   # words = [aaa, bbb, ccc, ddd]
+305   var w-storage: (handle word)
+306   var w-ah/esi: (addr handle word) <- address w-storage
+307   allocate-word-with w-ah, "aaa"
+308   append-word-at-end-with w-ah, "bbb"
+309   append-word-at-end-with w-ah, "ccc"
+310   append-word-at-end-with w-ah, "ddd"
+311   # setup: screen
+312   var screen-on-stack: screen
+313   var screen/edi: (addr screen) <- address screen-on-stack
+314   initialize-screen screen, 0x20, 4
+315   #
+316   var _w/eax: (addr word) <- lookup *w-ah
+317   var w/ecx: (addr word) <- copy _w
+318   var cursor-word/eax: int <- copy w
+319   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+320   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+321   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "   |                ", "F - test-render-words/0 cursor"
+322   # - start moving cursor left through final word
+323   cursor-left w
+324   var cursor-word/eax: int <- copy w
+325   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+326   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+327   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "  |                 ", "F - test-render-words/1 cursor"
+328   #
+329   cursor-left w
+330   var cursor-word/eax: int <- copy w
+331   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+332   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+333   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  " |                  ", "F - test-render-words/2 cursor"
+334   #
+335   cursor-left w
+336   var cursor-word/eax: int <- copy w
+337   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+338   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+339   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "|                   ", "F - test-render-words/3 cursor"
+340   # further moves left within the word change nothing
+341   cursor-left w
+342   var cursor-word/eax: int <- copy w
+343   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+344   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+345   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "|                   ", "F - test-render-words/4 cursor"
+346   # - switch to next word
+347   var w2-ah/eax: (addr handle word) <- get w, next
+348   var _w/eax: (addr word) <- lookup *w2-ah
+349   var w/ecx: (addr word) <- copy _w
+350   var cursor-word/eax: int <- copy w
+351   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+352   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+353   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "        |           ", "F - test-render-words/5 cursor"
+354   # now speed up a little
+355   cursor-left w
+356   cursor-left w
+357   var cursor-word/eax: int <- copy w
+358   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+359   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+360   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "      |             ", "F - test-render-words/6 cursor"
+361   #
+362   var w2-ah/eax: (addr handle word) <- get w, next
+363   var _w/eax: (addr word) <- lookup *w2-ah
+364   var w/ecx: (addr word) <- copy _w
+365   cursor-left w
+366   var cursor-word/eax: int <- copy w
+367   var new-x/eax: int <- render-words screen, w-ah, 0/x, 0/y, cursor-word
+368   check-screen-row screen, 0/y,                                   "aaa  bbb  ccc  ddd  ", "F - test-render-words/0"
+369   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "            |       ", "F - test-render-words/7 cursor"
+370 }
+371 
+372 fn render-words-in-reverse screen: (addr screen), _words-ah: (addr handle word), x: int, y: int, cursor-word-addr: int -> _/eax: int {
+373   var words-ah/eax: (addr handle word) <- copy _words-ah
+374   var _words-a/eax: (addr word) <- lookup *words-ah
+375   var words-a/ecx: (addr word) <- copy _words-a
+376   compare words-a, 0
+377   {
+378     break-if-!=
+379     return x
+380   }
+381   # recurse
+382   var next-ah/eax: (addr handle word) <- get words-a, next
+383   var next-x/eax: int <- render-words-in-reverse screen, next-ah, x, y, cursor-word-addr
+384   # print
+385   var render-cursor?/edx: boolean <- copy 0/false
+386   {
+387     compare cursor-word-addr, words-a
+388     break-if-!=
+389     render-cursor? <- copy 1/true
+390   }
+391   next-x <- render-word screen, words-a, next-x, y, render-cursor?
+392   var space/ecx: grapheme <- copy 0x20/space
+393   draw-grapheme screen, space, next-x, y, 3/fg=cyan, 0/bg
+394   next-x <- increment
+395   return next-x
+396 }
+397 
+398 fn test-render-words-in-reverse {
+399   # words = [aaa, bbb, ccc, ddd]
+400   var w-storage: (handle word)
+401   var w-ah/esi: (addr handle word) <- address w-storage
+402   allocate-word-with w-ah, "aaa"
+403   append-word-at-end-with w-ah, "bbb"
+404   append-word-at-end-with w-ah, "ccc"
+405   append-word-at-end-with w-ah, "ddd"
+406   # setup: screen
+407   var screen-on-stack: screen
+408   var screen/edi: (addr screen) <- address screen-on-stack
+409   initialize-screen screen, 0x20, 4
+410   #
+411   var _w/eax: (addr word) <- lookup *w-ah
+412   var w/ecx: (addr word) <- copy _w
+413   var cursor-word/eax: int <- copy w
+414   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+415   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/0"
+416   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "                  | ", "F - test-render-words-in-reverse/0 cursor"
+417   # - start moving cursor left through final word
+418   cursor-left w
+419   var cursor-word/eax: int <- copy w
+420   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+421   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/1"
+422   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "                 |  ", "F - test-render-words-in-reverse/1 cursor"
+423   #
+424   cursor-left w
+425   var cursor-word/eax: int <- copy w
+426   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+427   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/2"
+428   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "                |   ", "F - test-render-words-in-reverse/2 cursor"
+429   #
+430   cursor-left w
+431   var cursor-word/eax: int <- copy w
+432   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+433   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/3"
+434   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "               |    ", "F - test-render-words-in-reverse/3 cursor"
+435   # further moves left within the word change nothing
+436   cursor-left w
+437   var cursor-word/eax: int <- copy w
+438   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+439   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/4"
+440   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "               |    ", "F - test-render-words-in-reverse/4 cursor"
+441   # - switch to next word
+442   var w2-ah/eax: (addr handle word) <- get w, next
+443   var _w/eax: (addr word) <- lookup *w2-ah
+444   var w/ecx: (addr word) <- copy _w
+445   var cursor-word/eax: int <- copy w
+446   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+447   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/5"
+448   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "             |      ", "F - test-render-words-in-reverse/5 cursor"
+449   # now speed up a little
+450   cursor-left w
+451   cursor-left w
+452   var cursor-word/eax: int <- copy w
+453   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+454   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/6"
+455   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "           |        ", "F - test-render-words-in-reverse/6 cursor"
+456   #
+457   var w2-ah/eax: (addr handle word) <- get w, next
+458   var _w/eax: (addr word) <- lookup *w2-ah
+459   var w/ecx: (addr word) <- copy _w
+460   cursor-left w
+461   var cursor-word/eax: int <- copy w
+462   var new-x/eax: int <- render-words-in-reverse screen, w-ah, 0/x, 0/y, cursor-word
+463   check-screen-row screen, 0/y,                                   "ddd  ccc  bbb  aaa  ", "F - test-render-words-in-reverse/7"
+464   check-background-color-in-screen-row screen, 7/bg=cursor, 0/y,  "       |            ", "F - test-render-words-in-reverse/7 cursor"
+465 }
+466 
+467 # Gotcha with some word operations: ensure dest-ah isn't in the middle of some
+468 # existing chain of words. There are two pointers to patch, and you'll forget
+469 # to do the other one.
+470 fn copy-words _src-ah: (addr handle word), _dest-ah: (addr handle word) {
+471   var src-ah/eax: (addr handle word) <- copy _src-ah
+472   var src-a/eax: (addr word) <- lookup *src-ah
+473   compare src-a, 0
+474   break-if-=
+475   # copy
+476   var dest-ah/edi: (addr handle word) <- copy _dest-ah
+477   copy-word src-a, dest-ah
+478   # recurse
+479   var rest: (handle word)
+480   var rest-ah/ecx: (addr handle word) <- address rest
+481   var next-src-ah/esi: (addr handle word) <- get src-a, next
+482   copy-words next-src-ah, rest-ah
+483   chain-words dest-ah, rest-ah
+484 }
+485 
+486 fn copy-words-in-reverse _src-ah: (addr handle word), _dest-ah: (addr handle word) {
+487   var src-ah/eax: (addr handle word) <- copy _src-ah
+488   var _src-a/eax: (addr word) <- lookup *src-ah
+489   var src-a/esi: (addr word) <- copy _src-a
+490   compare src-a, 0
+491   break-if-=
+492   # recurse
+493   var next-src-ah/ecx: (addr handle word) <- get src-a, next
+494   var dest-ah/edi: (addr handle word) <- copy _dest-ah
+495   copy-words-in-reverse next-src-ah, dest-ah
+496   #
+497   copy-word-at-end src-a, dest-ah
+498 }
+499 
+500 fn copy-word-at-end src: (addr word), _dest-ah: (addr handle word) {
+501   var dest-ah/edi: (addr handle word) <- copy _dest-ah
+502   # if dest is null, copy and return
+503   var dest-a/eax: (addr word) <- lookup *dest-ah
+504   compare dest-a, 0
+505   {
+506     break-if-!=
+507     copy-word src, dest-ah
+508     return
+509   }
+510   # copy current word
+511   var new: (handle word)
+512   var new-ah/ecx: (addr handle word) <- address new
+513   copy-word src, new-ah
+514   # append it at the end
+515   var curr-ah/edi: (addr handle word) <- copy dest-ah
+516   {
+517     var curr-a/eax: (addr word) <- lookup *curr-ah  # curr-a guaranteed not to be null
+518     var next-ah/ecx: (addr handle word) <- get curr-a, next
+519     var next-a/eax: (addr word) <- lookup *next-ah
+520     compare next-a, 0
+521     break-if-=
+522     curr-ah <- copy next-ah
+523     loop
+524   }
+525   chain-words curr-ah, new-ah
+526 }
+527 
+528 fn append-word-at-end-with _dest-ah: (addr handle word), s: (addr array byte) {
+529   var dest-ah/edi: (addr handle word) <- copy _dest-ah
+530   # if dest is null, copy and return
+531   var dest-a/eax: (addr word) <- lookup *dest-ah
+532   compare dest-a, 0
+533   {
+534     break-if-!=
+535     allocate-word-with dest-ah, s
+536     return
+537   }
+538   # otherwise append at end
+539   var curr-ah/edi: (addr handle word) <- copy dest-ah
+540   {
+541     var curr-a/eax: (addr word) <- lookup *curr-ah  # curr-a guaranteed not to be null
+542     var next-ah/ecx: (addr handle word) <- get curr-a, next
+543     var next-a/eax: (addr word) <- lookup *next-ah
+544     compare next-a, 0
+545     break-if-=
+546     curr-ah <- copy next-ah
+547     loop
+548   }
+549   append-word-with *curr-ah, s
+550 }
+551 
+552 fn copy-word _src-a: (addr word), _dest-ah: (addr handle word) {
+553   var dest-ah/eax: (addr handle word) <- copy _dest-ah
+554   allocate dest-ah
+555   var _dest-a/eax: (addr word) <- lookup *dest-ah
+556   var dest-a/eax: (addr word) <- copy _dest-a
+557   initialize-word dest-a
+558   var dest/edi: (addr handle gap-buffer) <- get dest-a, scalar-data
+559   var src-a/eax: (addr word) <- copy _src-a
+560   var src/eax: (addr handle gap-buffer) <- get src-a, scalar-data
+561   copy-gap-buffer src, dest
+562 }
+563 
+564 # one implication of handles: append must take a handle
+565 fn append-word _self-ah: (addr handle word) {
+566   var saved-self-storage: (handle word)
+567   var saved-self/eax: (addr handle word) <- address saved-self-storage
+568   copy-object _self-ah, saved-self
+569   var self-ah/esi: (addr handle word) <- copy _self-ah
+570   var _self/eax: (addr word) <- lookup *self-ah
+571   var self/ebx: (addr word) <- copy _self
+572   # allocate new handle
+573   var new: (handle word)
+574   var new-ah/ecx: (addr handle word) <- address new
+575   allocate new-ah
+576   var new-addr/eax: (addr word) <- lookup new
+577   initialize-word new-addr
+578   # new->next = self->next
+579   var src/esi: (addr handle word) <- get self, next
+580   var dest/edi: (addr handle word) <- get new-addr, next
+581   copy-object src, dest
+582   # new->next->prev = new
+583   {
+584     var next-addr/eax: (addr word) <- lookup *src
+585     compare next-addr, 0
+586     break-if-=
+587     dest <- get next-addr, prev
+588     copy-object new-ah, dest
+589   }
+590   # new->prev = saved-self
+591   dest <- get new-addr, prev
+592   var saved-self-ah/eax: (addr handle word) <- address saved-self-storage
+593   copy-object saved-self-ah, dest
+594   # self->next = new
+595   dest <- get self, next
+596   copy-object new-ah, dest
+597 }
+598 
+599 fn chain-words _self-ah: (addr handle word), _next: (addr handle word) {
+600   var self-ah/esi: (addr handle word) <- copy _self-ah
+601   var _self/eax: (addr word) <- lookup *self-ah
+602   var self/ecx: (addr word) <- copy _self
+603   var dest/edx: (addr handle word) <- get self, next
+604   var next-ah/edi: (addr handle word) <- copy _next
+605   copy-object next-ah, dest
+606   var next/eax: (addr word) <- lookup *next-ah
+607   compare next, 0
+608   break-if-=
+609   dest <- get next, prev
+610   copy-object self-ah, dest
+611 }
+612 
+613 fn emit-word _self: (addr word), out: (addr stream byte) {
+614   var self/esi: (addr word) <- copy _self
+615   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+616   var data/eax: (addr gap-buffer) <- lookup *data-ah
+617   emit-gap-buffer data, out
+618 }
+619 
+620 fn word-is-decimal-integer? _self: (addr word) -> _/eax: boolean {
+621   var self/eax: (addr word) <- copy _self
+622   var data-ah/eax: (addr handle gap-buffer) <- get self, scalar-data
+623   var data/eax: (addr gap-buffer) <- lookup *data-ah
+624   var result/eax: boolean <- gap-buffer-is-decimal-integer? data
+625   return result
+626 }
+627 
+628 fn word-exists? haystack: (addr word), needle: (addr word) -> _/eax: boolean {
+629   # base case
+630   compare haystack, 0
+631   {
+632     break-if-!=
+633     return 0/false
+634   }
+635   # check current word
+636   var found?/eax: boolean <- words-equal? haystack, needle
+637   compare found?, 0/false
+638   {
+639     break-if-=
+640     return 1/true
+641   }
+642   # recurse
+643   var curr/eax: (addr word) <- copy haystack
+644   var next-ah/eax: (addr handle word) <- get curr, next
+645   var next/eax: (addr word) <- lookup *next-ah
+646   var result/eax: boolean <- word-exists? next, needle
+647   return result
+648 }
+649 
+650 fn test-word-exists? {
+651   var needle-storage: word
+652   var needle/esi: (addr word) <- address needle-storage
+653   initialize-word-with needle, "abc"
+654   var w-storage: (handle word)
+655   var w-ah/edi: (addr handle word) <- address w-storage
+656   allocate w-ah
+657   var _w/eax: (addr word) <- lookup *w-ah
+658   var w/ecx: (addr word) <- copy _w
+659   initialize-word-with w, "aaa"
+660   #
+661   var result/eax: boolean <- word-exists? w, w
+662   check result, "F - test-word-exists? reflexive"
+663   result <- word-exists? w, needle
+664   check-not result, "F - test-word-exists? 1"
+665   append-word-at-end-with w-ah, "bbb"
+666   result <- word-exists? w, needle
+667   check-not result, "F - test-word-exists? 2"
+668   append-word-at-end-with w-ah, "abc"
+669   result <- word-exists? w, needle
+670   check result, "F - test-word-exists? 3"
+671   append-word-at-end-with w-ah, "ddd"
+672   result <- word-exists? w, needle
+673   check result, "F - test-word-exists? 4"
+674 }
+675 
+676 fn word-list-length words: (addr handle word) -> _/eax: int {
+677   var curr-ah/esi: (addr handle word) <- copy words
+678   var result/edi: int <- copy 0
+679   {
+680     var curr/eax: (addr word) <- lookup *curr-ah
+681     compare curr, 0
+682     break-if-=
+683     {
+684       var word-len/eax: int <- word-length curr
+685       result <- add word-len
+686       result <- add 1/inter-word-margin
+687     }
+688     curr-ah <- get curr, next
+689     loop
+690   }
+691   return result
+692 }
+693 
+694 # out-ah already has a word allocated and initialized
+695 fn parse-words in: (addr array byte), out-ah: (addr handle word) {
+696   var in-stream: (stream byte 0x100)
+697   var in-stream-a/esi: (addr stream byte) <- address in-stream
+698   write in-stream-a, in
+699   var cursor-word-ah/ebx: (addr handle word) <- copy out-ah
+700   $parse-words:loop: {
+701     var done?/eax: boolean <- stream-empty? in-stream-a
+702     compare done?, 0/false
+703     break-if-!=
+704     var _g/eax: grapheme <- read-grapheme in-stream-a
+705     var g/ecx: grapheme <- copy _g
+706     # if not space, insert
+707     compare g, 0x20/space
+708     {
+709       break-if-=
+710       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
+711       add-grapheme-to-word cursor-word, g
+712       loop $parse-words:loop
+713     }
+714     # otherwise insert word after and move cursor to it
+715     append-word cursor-word-ah
+716     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
+717     cursor-to-start cursor-word  # reset cursor in each function
+718     cursor-word-ah <- get cursor-word, next
+719     loop
+720   }
+721 }
+
+ + + -- cgit 1.4.1-2-gfad0