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 }