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 }