https://github.com/akkartik/mu/blob/main/linux/tile/data.mu
  1 # widgets in the environment share the following pattern of updates:
  2 #   process-* functions read keys and update which object the cursor is at
  3 #   render-* functions print to screen and update which row/col each object's cursor is at
  4 
  5 type sandbox {
  6   setup: (handle line)
  7   data: (handle line)
  8   # bookkeeping for process-*
  9   cursor-call-path: (handle call-path-element)
 10   expanded-words: (handle call-path)
 11   partial-name-for-cursor-word: (handle word)  # only when renaming word
 12   partial-name-for-function: (handle word)  # only when defining function
 13   # bookkeeping for render-*
 14   cursor-row: int
 15   cursor-col: int
 16   #
 17   next: (handle sandbox)
 18   prev: (handle sandbox)
 19 }
 20 
 21 type function {
 22   name: (handle array byte)
 23   args: (handle word)  # in reverse order
 24   body: (handle line)
 25   # bookkeeping for process-*
 26   cursor-word: (handle word)
 27   # bookkeeping for render-*
 28   cursor-row: int
 29   cursor-col: int
 30   # todo: some sort of indication of spatial location
 31   next: (handle function)
 32 }
 33 
 34 type line {
 35   name: (handle array byte)
 36   data: (handle word)
 37   result: (handle result)  # might be cached
 38   next: (handle line)
 39   prev: (handle line)
 40 }
 41 
 42 type word {
 43   scalar-data: (handle gap-buffer)
 44   next: (handle word)
 45   prev: (handle word)
 46 }
 47 
 48 # todo: turn this into a sum type
 49 type value {
 50   type: int
 51   number-data: float  # if type = 0
 52   text-data: (handle array byte)  # if type = 1
 53   array-data: (handle array value)  # if type = 2
 54   file-data: (handle buffered-file)  # if type = 3
 55   filename: (handle array byte)  # if type = 3
 56   screen-data: (handle screen)  # if type = 4
 57 }
 58 
 59 type table {
 60   data: (handle array bind)
 61   next: (handle table)
 62 }
 63 
 64 type bind {
 65   key: (handle array byte)
 66   value: (handle value)  # I'd inline this but we sometimes want to return a specific value from a table
 67 }
 68 
 69 # A call-path is a data structure that can unambiguously refer to any specific
 70 # call arbitrarily deep inside the call hierarchy of a program.
 71 type call-path {
 72   data: (handle call-path-element)
 73   next: (handle call-path)
 74 }
 75 
 76 # A call-path element is a list of elements, each of which corresponds to some call.
 77 type call-path-element {
 78   word: (handle word)
 79   next: (handle call-path-element)
 80 }
 81 
 82 type result {
 83   data: value-stack
 84   error: (handle array byte)  # single error message for now
 85 }
 86 
 87 fn initialize-sandbox _sandbox: (addr sandbox) {
 88   var sandbox/esi: (addr sandbox) <- copy _sandbox
 89   var line-ah/eax: (addr handle line) <- get sandbox, data
 90   allocate line-ah
 91   var line/eax: (addr line) <- lookup *line-ah
 92   initialize-line line
 93   var word-ah/ecx: (addr handle word) <- get line, data
 94   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 95   allocate cursor-call-path-ah
 96   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 97   var dest/eax: (addr handle word) <- get cursor-call-path, word
 98   copy-object word-ah, dest
 99 }
100 
101 # initialize line with a single empty word
102 fn initialize-line _line: (addr line) {
103   var line/esi: (addr line) <- copy _line
104   var word-ah/eax: (addr handle word) <- get line, data
105   allocate word-ah
106   var word/eax: (addr word) <- lookup *word-ah
107   initialize-word word
108 }
109 
110 fn create-primitive-functions _self: (addr handle function) {
111   # x 2* = x 2 *
112   var self/esi: (addr handle function) <- copy _self
113   allocate self
114   var _f/eax: (addr function) <- lookup *self
115   var f/esi: (addr function) <- copy _f
116   var name-ah/eax: (addr handle array byte) <- get f, name
117   populate-text-with name-ah, "2*"
118   var args-ah/eax: (addr handle word) <- get f, args
119   allocate args-ah
120   var args/eax: (addr word) <- lookup *args-ah
121   initialize-word-with args, "x"
122   var body-ah/eax: (addr handle line) <- get f, body
123   allocate body-ah
124   var body/eax: (addr line) <- lookup *body-ah
125   initialize-line body
126   var curr-word-ah/ecx: (addr handle word) <- get body, data
127   parse-words "x 2 *", curr-word-ah
128   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
129   copy-object curr-word-ah, cursor-word-ah
130   # x 1+ = x 1 +
131   var next/esi: (addr handle function) <- get f, next
132   allocate next
133   var _f/eax: (addr function) <- lookup *next
134   var f/esi: (addr function) <- copy _f
135   var name-ah/eax: (addr handle array byte) <- get f, name
136   populate-text-with name-ah, "1+"
137   var args-ah/eax: (addr handle word) <- get f, args
138   allocate args-ah
139   var args/eax: (addr word) <- lookup *args-ah
140   initialize-word-with args, "x"
141   var body-ah/eax: (addr handle line) <- get f, body
142   allocate body-ah
143   var body/eax: (addr line) <- lookup *body-ah
144   initialize-line body
145   curr-word-ah <- get body, data
146   parse-words "x 1 +", curr-word-ah
147   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
148   copy-object curr-word-ah, cursor-word-ah
149   # x 2+ = x 1+ 1+
150   var next/esi: (addr handle function) <- get f, next
151   allocate next
152   var _f/eax: (addr function) <- lookup *next
153   var f/esi: (addr function) <- copy _f
154   var name-ah/eax: (addr handle array byte) <- get f, name
155   populate-text-with name-ah, "2+"
156   var args-ah/eax: (addr handle word) <- get f, args
157   allocate args-ah
158   var args/eax: (addr word) <- lookup *args-ah
159   initialize-word-with args, "x"
160   var body-ah/eax: (addr handle line) <- get f, body
161   allocate body-ah
162   var body/eax: (addr line) <- lookup *body-ah
163   initialize-line body
164   curr-word-ah <- get body, data
165   parse-words "x 1+ 1+", curr-word-ah
166   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
167   copy-object curr-word-ah, cursor-word-ah
168   # x square = x x *
169   var next/esi: (addr handle function) <- get f, next
170   allocate next
171   var _f/eax: (addr function) <- lookup *next
172   var f/esi: (addr function) <- copy _f
173   var name-ah/eax: (addr handle array byte) <- get f, name
174   populate-text-with name-ah, "square"
175   var args-ah/eax: (addr handle word) <- get f, args
176   allocate args-ah
177   var args/eax: (addr word) <- lookup *args-ah
178   initialize-word-with args, "x"
179   var body-ah/eax: (addr handle line) <- get f, body
180   allocate body-ah
181   var body/eax: (addr line) <- lookup *body-ah
182   initialize-line body
183   curr-word-ah <- get body, data
184   parse-words "x x *", curr-word-ah
185   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
186   copy-object curr-word-ah, cursor-word-ah
187   # x 1- = x 1 -
188   var next/esi: (addr handle function) <- get f, next
189   allocate next
190   var _f/eax: (addr function) <- lookup *next
191   var f/esi: (addr function) <- copy _f
192   var name-ah/eax: (addr handle array byte) <- get f, name
193   populate-text-with name-ah, "1-"
194   var args-ah/eax: (addr handle word) <- get f, args
195   allocate args-ah
196   var args/eax: (addr word) <- lookup *args-ah
197   initialize-word-with args, "x"
198   var body-ah/eax: (addr handle line) <- get f, body
199   allocate body-ah
200   var body/eax: (addr line) <- lookup *body-ah
201   initialize-line body
202   curr-word-ah <- get body, data
203   parse-words "x 1 -", curr-word-ah
204   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
205   copy-object curr-word-ah, cursor-word-ah
206   # x y sub = x y -
207   var next/esi: (addr handle function) <- get f, next
208   allocate next
209   var _f/eax: (addr function) <- lookup *next
210   var f/esi: (addr function) <- copy _f
211   var name-ah/eax: (addr handle array byte) <- get f, name
212   populate-text-with name-ah, "sub"
213   # critical lesson: args are stored in reverse order
214   var args-ah/eax: (addr handle word) <- get f, args
215   allocate args-ah
216   var args/eax: (addr word) <- lookup *args-ah
217   initialize-word-with args, "y"
218   var next-arg-ah/eax: (addr handle word) <- get args, next
219   allocate next-arg-ah
220   var next-arg/eax: (addr word) <- lookup *next-arg-ah
221   initialize-word-with next-arg, "x"
222   var body-ah/eax: (addr handle line) <- get f, body
223   allocate body-ah
224   var body/eax: (addr line) <- lookup *body-ah
225   initialize-line body
226   curr-word-ah <- get body, data
227   parse-words "x y -", curr-word-ah
228   var cursor-word-ah/edx: (addr handle word) <- get f, cursor-word
229   copy-object curr-word-ah, cursor-word-ah
230 }
231 
232 fn function-body functions: (addr handle function), _word: (addr handle word), out: (addr handle line) {
233   var function-name-storage: (handle array byte)
234   var function-name-ah/ecx: (addr handle array byte) <- address function-name-storage
235   var word-ah/esi: (addr handle word) <- copy _word
236   var word/eax: (addr word) <- lookup *word-ah
237   var gap-ah/eax: (addr handle gap-buffer) <- get word, scalar-data
238   var gap/eax: (addr gap-buffer) <- lookup *gap-ah
239   gap-buffer-to-string gap, function-name-ah
240   var _function-name/eax: (addr array byte) <- lookup *function-name-ah
241   var function-name/esi: (addr array byte) <- copy _function-name
242   var curr-ah/ecx: (addr handle function) <- copy functions
243   $function-body:loop: {
244     var _curr/eax: (addr function) <- lookup *curr-ah
245     var curr/edx: (addr function) <- copy _curr
246     compare curr, 0
247     break-if-=
248     var curr-name-ah/eax: (addr handle array byte) <- get curr, name
249     var curr-name/eax: (addr array byte) <- lookup *curr-name-ah
250     var found?/eax: boolean <- string-equal? curr-name, function-name
251     compare found?, 0/false
252     {
253       break-if-=
254       var src/eax: (addr handle line) <- get curr, body
255       copy-object src, out
256       break $function-body:loop
257     }
258     curr-ah <- get curr, next
259     loop
260   }
261 }
262 
263 fn body-length functions: (addr handle function), function-name: (addr handle word) -> _/eax: int {
264   var body-storage: (handle line)
265   var body-ah/edi: (addr handle line) <- address body-storage
266   function-body functions, function-name, body-ah
267   var body/eax: (addr line) <- lookup *body-ah
268   var result/eax: int <- num-words-in-line body
269   return result
270 }
271 
272 fn num-words-in-line _in: (addr line) -> _/eax: int {
273   var in/esi: (addr line) <- copy _in
274   var curr-ah/ecx: (addr handle word) <- get in, data
275   var result/edi: int <- copy 0
276   {
277     var curr/eax: (addr word) <- lookup *curr-ah
278     compare curr, 0
279     break-if-=
280     curr-ah <- get curr, next
281     result <- increment
282     loop
283   }
284   return result
285 }
286 
287 fn populate-text-with _out: (addr handle array byte), _in: (addr array byte) {
288   var in/esi: (addr array byte) <- copy _in
289   var n/ecx: int <- length in
290   var out/edx: (addr handle array byte) <- copy _out
291   populate out, n
292   var _out-addr/eax: (addr array byte) <- lookup *out
293   var out-addr/edx: (addr array byte) <- copy _out-addr
294   var i/eax: int <- copy 0
295   {
296     compare i, n
297     break-if->=
298     var src/esi: (addr byte) <- index in, i
299     var val/ecx: byte <- copy-byte *src
300     var dest/edi: (addr byte) <- index out-addr, i
301     copy-byte-to *dest, val
302     i <- increment
303     loop
304   }
305 }
306 
307 fn initialize-path-from-sandbox _in: (addr sandbox), _out: (addr handle call-path-element) {
308   var sandbox/esi: (addr sandbox) <- copy _in
309   var line-ah/eax: (addr handle line) <- get sandbox, data
310   var line/eax: (addr line) <- lookup *line-ah
311   var src/esi: (addr handle word) <- get line, data
312   var out-ah/edi: (addr handle call-path-element) <- copy _out
313   var out/eax: (addr call-path-element) <- lookup *out-ah
314   var dest/edi: (addr handle word) <- get out, word
315   copy-object src, dest
316 }
317 
318 fn initialize-path-from-line _line: (addr line), _out: (addr handle call-path-element) {
319   var line/eax: (addr line) <- copy _line
320   var src/esi: (addr handle word) <- get line, data
321   var out-ah/edi: (addr handle call-path-element) <- copy _out
322   var out/eax: (addr call-path-element) <- lookup *out-ah
323   var dest/edi: (addr handle word) <- get out, word
324   copy-object src, dest
325 }
326 
327 fn find-in-call-paths call-paths: (addr handle call-path), needle: (addr handle call-path-element) -> _/eax: boolean {
328   var curr-ah/esi: (addr handle call-path) <- copy call-paths
329   $find-in-call-path:loop: {
330     var curr/eax: (addr call-path) <- lookup *curr-ah
331     compare curr, 0
332     break-if-=
333     {
334       var curr-data/eax: (addr handle call-path-element) <- get curr, data
335       var match?/eax: boolean <- call-path-element-match? curr-data, needle
336       compare match?, 0/false
337       {
338         break-if-=
339         return 1/true
340       }
341     }
342     curr-ah <- get curr, next
343     loop
344   }
345   return 0/false
346 }
347 
348 fn call-path-element-match? _x: (addr handle call-path-element), _y: (addr handle call-path-element) -> _/eax: boolean {
349   var x-ah/eax: (addr handle call-path-element) <- copy _x
350   var x-a/eax: (addr call-path-element) <- lookup *x-ah
351   var x/esi: (addr call-path-element) <- copy x-a
352   var y-ah/eax: (addr handle call-path-element) <- copy _y
353   var y-a/eax: (addr call-path-element) <- lookup *y-ah
354   var y/edi: (addr call-path-element) <- copy y-a
355   compare x, y
356   {
357     break-if-!=
358     return 1/true
359   }
360   compare x, 0
361   {
362     break-if-!=
363     return 0/false
364   }
365   compare y, 0
366   {
367     break-if-!=
368     return 0/false
369   }
370   # compare word addresses, not contents
371   var x-data-ah/ecx: (addr handle word) <- get x, word
372   var x-data-a/eax: (addr word) <- lookup *x-data-ah
373   var x-data/ecx: int <- copy x-data-a
374   var y-data-ah/eax: (addr handle word) <- get y, word
375   var y-data-a/eax: (addr word) <- lookup *y-data-ah
376   var y-data/eax: int <- copy y-data-a
377 #?   print-string 0, "match? "
378 #?   print-int32-hex 0, x-data
379 #?   print-string 0, " vs "
380 #?   print-int32-hex 0, y-data
381 #?   print-string 0, "\n"
382   compare x-data, y-data
383   {
384     break-if-=
385     return 0/false
386   }
387   var x-next/ecx: (addr handle call-path-element) <- get x, next
388   var y-next/eax: (addr handle call-path-element) <- get y, next
389   var result/eax: boolean <- call-path-element-match? x-next, y-next
390   return result
391 }
392 
393 # order is irrelevant
394 fn insert-in-call-path list: (addr handle call-path), new: (addr handle call-path-element) {
395   var new-path-storage: (handle call-path)
396   var new-path-ah/edi: (addr handle call-path) <- address new-path-storage
397   allocate new-path-ah
398   var new-path/eax: (addr call-path) <- lookup *new-path-ah
399   var next/ecx: (addr handle call-path) <- get new-path, next
400   copy-object list, next
401   var dest/ecx: (addr handle call-path-element) <- get new-path, data
402   deep-copy-call-path-element new, dest
403   copy-object new-path-ah, list
404 }
405 
406 # assumes dest is initially clear
407 fn deep-copy-call-path-element _src: (addr handle call-path-element), _dest: (addr handle call-path-element) {
408   var src/esi: (addr handle call-path-element) <- copy _src
409   # if src is null, return
410   var _src-addr/eax: (addr call-path-element) <- lookup *src
411   compare _src-addr, 0
412   break-if-=
413   # allocate
414   var src-addr/esi: (addr call-path-element) <- copy _src-addr
415   var dest/eax: (addr handle call-path-element) <- copy _dest
416   allocate dest
417   # copy data
418   var dest-addr/eax: (addr call-path-element) <- lookup *dest
419   {
420     var dest-data-addr/ecx: (addr handle word) <- get dest-addr, word
421     var src-data-addr/eax: (addr handle word) <- get src-addr, word
422     copy-object src-data-addr, dest-data-addr
423   }
424   # recurse
425   var src-next/esi: (addr handle call-path-element) <- get src-addr, next
426   var dest-next/eax: (addr handle call-path-element) <- get dest-addr, next
427   deep-copy-call-path-element src-next, dest-next
428 }
429 
430 fn delete-in-call-path list: (addr handle call-path), needle: (addr handle call-path-element) {
431   var curr-ah/esi: (addr handle call-path) <- copy list
432   $delete-in-call-path:loop: {
433     var _curr/eax: (addr call-path) <- lookup *curr-ah
434     var curr/ecx: (addr call-path) <- copy _curr
435     compare curr, 0
436     break-if-=
437     {
438       var curr-data/eax: (addr handle call-path-element) <- get curr, data
439       var match?/eax: boolean <- call-path-element-match? curr-data, needle
440       compare match?, 0/false
441       {
442         break-if-=
443         var next-ah/ecx: (addr handle call-path) <- get curr, next
444         copy-object next-ah, curr-ah
445         loop $delete-in-call-path:loop
446       }
447     }
448     curr-ah <- get curr, next
449     loop
450   }
451 }
452 
453 fn increment-final-element list: (addr handle call-path-element) {
454   var final-ah/eax: (addr handle call-path-element) <- copy list
455   var final/eax: (addr call-path-element) <- lookup *final-ah
456   var val-ah/ecx: (addr handle word) <- get final, word
457   var val/eax: (addr word) <- lookup *val-ah
458   var new-ah/edx: (addr handle word) <- get val, next
459   var target/eax: (addr word) <- lookup *new-ah
460   compare target, 0
461   break-if-=
462   copy-object new-ah, val-ah
463 }
464 
465 fn decrement-final-element list: (addr handle call-path-element) {
466   var final-ah/eax: (addr handle call-path-element) <- copy list
467   var final/eax: (addr call-path-element) <- lookup *final-ah
468   var val-ah/ecx: (addr handle word) <- get final, word
469   var val/eax: (addr word) <- lookup *val-ah
470 #?   print-string 0, "replacing "
471 #?   {
472 #?     var foo/eax: int <- copy val
473 #?     print-int32-hex 0, foo
474 #?   }
475   var new-ah/edx: (addr handle word) <- get val, prev
476   var target/eax: (addr word) <- lookup *new-ah
477   compare target, 0
478   break-if-=
479   # val = val->prev
480 #?   print-string 0, " with "
481 #?   {
482 #?     var foo/eax: int <- copy target
483 #?     print-int32-hex 0, foo
484 #?   }
485 #?   print-string 0, "\n"
486   copy-object new-ah, val-ah
487 }
488 
489 fn move-final-element-to-start-of-line list: (addr handle call-path-element) {
490   var final-ah/eax: (addr handle call-path-element) <- copy list
491   var final/eax: (addr call-path-element) <- lookup *final-ah
492   var val-ah/ecx: (addr handle word) <- get final, word
493   var val/eax: (addr word) <- lookup *val-ah
494   var new-ah/edx: (addr handle word) <- get val, prev
495   var target/eax: (addr word) <- lookup *new-ah
496   compare target, 0
497   break-if-=
498   copy-object new-ah, val-ah
499   move-final-element-to-start-of-line list
500 }
501 
502 fn move-final-element-to-end-of-line list: (addr handle call-path-element) {
503   var final-ah/eax: (addr handle call-path-element) <- copy list
504   var final/eax: (addr call-path-element) <- lookup *final-ah
505   var val-ah/ecx: (addr handle word) <- get final, word
506   var val/eax: (addr word) <- lookup *val-ah
507   var new-ah/edx: (addr handle word) <- get val, next
508   var target/eax: (addr word) <- lookup *new-ah
509   compare target, 0
510   break-if-=
511   copy-object new-ah, val-ah
512   move-final-element-to-end-of-line list
513 }
514 
515 fn push-to-call-path-element list: (addr handle call-path-element), new: (addr handle word) {
516   var new-element-storage: (handle call-path-element)
517   var new-element-ah/edi: (addr handle call-path-element) <- address new-element-storage
518   allocate new-element-ah
519   var new-element/eax: (addr call-path-element) <- lookup *new-element-ah
520   # save word
521   var dest/ecx: (addr handle word) <- get new-element, word
522   copy-object new, dest
523   # save next
524   var dest2/ecx: (addr handle call-path-element) <- get new-element, next
525   copy-object list, dest2
526   # return
527   copy-object new-element-ah, list
528 }
529 
530 fn drop-from-call-path-element _list: (addr handle call-path-element) {
531   var list-ah/esi: (addr handle call-path-element) <- copy _list
532   var list/eax: (addr call-path-element) <- lookup *list-ah
533   var next/eax: (addr handle call-path-element) <- get list, next
534   copy-object next, _list
535 }
536 
537 fn drop-nested-calls _list: (addr handle call-path-element) {
538   var list-ah/esi: (addr handle call-path-element) <- copy _list
539   var list/eax: (addr call-path-element) <- lookup *list-ah
540   var next-ah/edi: (addr handle call-path-element) <- get list, next
541   var next/eax: (addr call-path-element) <- lookup *next-ah
542   compare next, 0
543   break-if-=
544   copy-object next-ah, _list
545   drop-nested-calls _list
546 }
547 
548 fn dump-call-path-element screen: (addr screen), _x-ah: (addr handle call-path-element) {
549   var x-ah/ecx: (addr handle call-path-element) <- copy _x-ah
550   var _x/eax: (addr call-path-element) <- lookup *x-ah
551   var x/esi: (addr call-path-element) <- copy _x
552   var word-ah/eax: (addr handle word) <- get x, word
553   var word/eax: (addr word) <- lookup *word-ah
554   print-word screen, word
555   var next-ah/ecx: (addr handle call-path-element) <- get x, next
556   var next/eax: (addr call-path-element) <- lookup *next-ah
557   compare next, 0
558   {
559     break-if-=
560     print-string screen, " "
561     dump-call-path-element screen, next-ah
562     return
563   }
564   print-string screen, "\n"
565 }
566 
567 fn dump-call-paths screen: (addr screen), _x-ah: (addr handle call-path) {
568   var x-ah/ecx: (addr handle call-path) <- copy _x-ah
569   var x/eax: (addr call-path) <- lookup *x-ah
570   compare x, 0
571   break-if-=
572   var src/ecx: (addr handle call-path-element) <- get x, data
573   dump-call-path-element screen, src
574   var next-ah/ecx: (addr handle call-path) <- get x, next
575   var next/eax: (addr call-path) <- lookup *next-ah
576   compare next, 0
577   {
578     break-if-=
579     dump-call-paths screen, next-ah
580   }
581 }
582 
583 fn function-width _self: (addr function) -> _/eax: int {
584   var self/esi: (addr function) <- copy _self
585   var args/ecx: (addr handle word) <- get self, args
586   var arg-width/eax: int <- word-list-length args
587   var result/edi: int <- copy arg-width
588   result <- add 4  # function-header-indent + body-indent
589   var body-ah/eax: (addr handle line) <- get self, body
590   var body-width/eax: int <- body-width body-ah
591   body-width <- add 1  # right margin
592   body-width <- add 2  # body-indent for "≡ "
593   compare result, body-width
594   {
595     break-if->=
596     result <- copy body-width
597   }
598   return result
599 }
600 
601 fn body-width lines: (addr handle line) -> _/eax: int {
602   var curr-ah/esi: (addr handle line) <- copy lines
603   var result/edi: int <- copy 0
604   {
605     var curr/eax: (addr line) <- lookup *curr-ah
606     compare curr, 0
607     break-if-=
608     {
609       var words/ecx: (addr handle word) <- get curr, data
610       var curr-len/eax: int <- word-list-length words
611       compare curr-len, result
612       break-if-<=
613       result <- copy curr-len
614     }
615     curr-ah <- get curr, next
616     loop
617   }
618   return result
619 }
620 
621 fn function-height _self: (addr function) -> _/eax: int {
622   var self/esi: (addr function) <- copy _self
623   var body-ah/eax: (addr handle line) <- get self, body
624   var result/eax: int <- line-list-length body-ah
625   result <- increment  # for function header
626   return result
627 }
628 
629 fn line-list-length lines: (addr handle line) -> _/eax: int {
630   var curr-ah/esi: (addr handle line) <- copy lines
631   var result/edi: int <- copy 0
632   {
633     var curr/eax: (addr line) <- lookup *curr-ah
634     compare curr, 0
635     break-if-=
636     curr-ah <- get curr, next
637     result <- increment
638     loop
639   }
640   return result
641 }