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 }