https://github.com/akkartik/mu/blob/master/apps/tile/environment.mu
   1 type environment {
   2   screen: (handle screen)
   3   functions: (handle function)
   4   sandboxes: (handle sandbox)
   5   cursor-sandbox: (handle sandbox)
   6   nrows: int
   7   ncols: int
   8   code-separator-col: int
   9 }
  10 
  11 fn initialize-environment _env: (addr environment) {
  12   var env/esi: (addr environment) <- copy _env
  13   # initialize some predefined function definitions
  14   var functions/eax: (addr handle function) <- get env, functions
  15   create-primitive-functions functions
  16   # initialize first sandbox
  17   var sandbox-ah/ecx: (addr handle sandbox) <- get env, sandboxes
  18   allocate sandbox-ah
  19   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
  20   initialize-sandbox sandbox
  21   # initialize cursor sandbox
  22   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
  23   copy-object sandbox-ah, cursor-sandbox-ah
  24   # initialize screen
  25   var screen-ah/eax: (addr handle screen) <- get env, screen
  26   var _screen/eax: (addr screen) <- lookup *screen-ah
  27   var screen/edi: (addr screen) <- copy _screen
  28   var nrows/eax: int <- copy 0
  29   var ncols/ecx: int <- copy 0
  30   nrows, ncols <- screen-size screen
  31   var dest/edx: (addr int) <- get env, nrows
  32   copy-to *dest, nrows
  33   dest <- get env, ncols
  34   copy-to *dest, ncols
  35   var repl-col/ecx: int <- copy ncols
  36   repl-col <- shift-right 1
  37   dest <- get env, code-separator-col
  38   copy-to *dest, repl-col
  39 }
  40 
  41 fn draw-screen _env: (addr environment) {
  42   var env/esi: (addr environment) <- copy _env
  43   var screen-ah/eax: (addr handle screen) <- get env, screen
  44   var _screen/eax: (addr screen) <- lookup *screen-ah
  45   var screen/edi: (addr screen) <- copy _screen
  46   var dest/edx: (addr int) <- get env, code-separator-col
  47   var tmp/eax: int <- copy *dest
  48   clear-canvas env
  49   tmp <- add 2  # repl-margin-left
  50   move-cursor screen, 3, tmp  # input-row
  51 }
  52 
  53 fn initialize-environment-with-fake-screen _self: (addr environment), nrows: int, ncols: int {
  54   var self/esi: (addr environment) <- copy _self
  55   var screen-ah/eax: (addr handle screen) <- get self, screen
  56   allocate screen-ah
  57   var screen-addr/eax: (addr screen) <- lookup *screen-ah
  58   initialize-screen screen-addr, nrows, ncols
  59   initialize-environment self
  60 }
  61 
  62 #############
  63 # Iterate
  64 #############
  65 
  66 fn process _self: (addr environment), key: grapheme {
  67   var self/esi: (addr environment) <- copy _self
  68   var sandbox-ah/eax: (addr handle sandbox) <- get self, cursor-sandbox
  69   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
  70 #?   print-string 0, "processing sandbox\n"
  71   process-sandbox self, sandbox, key
  72 }
  73 
  74 fn process-sandbox _self: (addr environment), _sandbox: (addr sandbox), key: grapheme {
  75   var self/esi: (addr environment) <- copy _self
  76   var sandbox/edi: (addr sandbox) <- copy _sandbox
  77   var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
  78   var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah?
  79   compare rename-word-mode?, 0
  80   {
  81     break-if-=
  82 #?     print-string 0, "processing sandbox rename\n"
  83     process-sandbox-rename sandbox, key
  84     return
  85   }
  86   var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function
  87   var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah?
  88   compare define-function-mode?, 0
  89   {
  90     break-if-=
  91 #?     print-string 0, "processing function definition\n"
  92     var functions/ecx: (addr handle function) <- get self, functions
  93     process-sandbox-define sandbox, functions, key
  94     return
  95   }
  96 #?   print-string 0, "processing sandbox edit\n"
  97   process-sandbox-edit self, sandbox, key
  98 }
  99 
 100 fn process-sandbox-edit _self: (addr environment), _sandbox: (addr sandbox), key: grapheme {
 101   var self/esi: (addr environment) <- copy _self
 102   var sandbox/edi: (addr sandbox) <- copy _sandbox
 103   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 104   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 105   var cursor-word-ah/ebx: (addr handle word) <- get cursor-call-path, word
 106   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 107   var cursor-word/ecx: (addr word) <- copy _cursor-word
 108   compare key, 0x445b1b  # left-arrow
 109   $process-sandbox-edit:key-left-arrow: {
 110     break-if-!=
 111 #?     print-string 0, "left-arrow\n"
 112     # if not at start, move left within current word
 113     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 114     compare at-start?, 0  # false
 115     {
 116       break-if-!=
 117 #?       print-string 0, "cursor left within word\n"
 118       cursor-left cursor-word
 119       return
 120     }
 121     # if current word is expanded, move to the rightmost word in its body
 122     {
 123       var cursor-call-path/esi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 124       var expanded-words/edx: (addr handle call-path) <- get sandbox, expanded-words
 125       var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 126       compare curr-word-is-expanded?, 0  # false
 127       break-if-=
 128       # update cursor-call-path
 129 #?       print-string 0, "curr word is expanded\n"
 130       var self/ecx: (addr environment) <- copy _self
 131       var functions/ecx: (addr handle function) <- get self, functions
 132       var body: (handle line)
 133       var body-ah/eax: (addr handle line) <- address body
 134       function-body functions, cursor-word-ah, body-ah
 135       var body-addr/eax: (addr line) <- lookup *body-ah
 136       var first-word-ah/edx: (addr handle word) <- get body-addr, data
 137       var final-word-h: (handle word)
 138       var final-word-ah/eax: (addr handle word) <- address final-word-h
 139       final-word first-word-ah, final-word-ah
 140       push-to-call-path-element cursor-call-path, final-word-ah
 141       # move cursor to end of word
 142       var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 143       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 144       var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 145       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 146       cursor-to-end cursor-word
 147       return
 148     }
 149     # if at first word, look for a caller to jump to
 150     $process-sandbox-edit:key-left-arrow-first-word: {
 151       var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 152       var prev-word/eax: (addr word) <- lookup *prev-word-ah
 153       compare prev-word, 0
 154       break-if-!=
 155       $process-sandbox-edit:key-left-arrow-first-word-and-caller: {
 156 #?         print-string 0, "return\n"
 157         {
 158           var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 159           var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 160           var next-cursor-element-ah/edx: (addr handle call-path-element) <- get cursor-call-path, next
 161           var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 162           compare next-cursor-element, 0
 163           break-if-= $process-sandbox-edit:key-left-arrow-first-word-and-caller
 164           copy-object next-cursor-element-ah, cursor-call-path-ah
 165         }
 166         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 167         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 168         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 169         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 170         cursor-word <- copy _cursor-word
 171       }
 172     }
 173     # then move to end of previous word
 174     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 175     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 176     {
 177       compare prev-word, 0
 178       break-if-=
 179 #?       print-string 0, "move to previous word\n"
 180       cursor-to-end prev-word
 181 #?       {
 182 #?         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 183 #?         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 184 #?         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 185 #?         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 186 #?         var cursor-word/ebx: (addr word) <- copy _cursor-word
 187 #?         print-string 0, "word at cursor before: "
 188 #?         print-word 0, cursor-word
 189 #?         print-string 0, "\n"
 190 #?       }
 191       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 192       decrement-final-element cursor-call-path
 193 #?       {
 194 #?         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 195 #?         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 196 #?         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 197 #?         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 198 #?         var cursor-word/ebx: (addr word) <- copy _cursor-word
 199 #?         print-string 0, "word at cursor after: "
 200 #?         print-word 0, cursor-word
 201 #?         print-string 0, "\n"
 202 #?       }
 203     }
 204     return
 205   }
 206   compare key, 0x435b1b  # right-arrow
 207   $process-sandbox-edit:key-right-arrow: {
 208     break-if-!=
 209     # if not at end, move right within current word
 210     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 211     compare at-end?, 0  # false
 212     {
 213       break-if-!=
 214 #?       print-string 0, "a\n"
 215       cursor-right cursor-word
 216       return
 217     }
 218     # if at final word, look for a caller to jump to
 219     {
 220       var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 221       var next-word/eax: (addr word) <- lookup *next-word-ah
 222       compare next-word, 0
 223       break-if-!=
 224       var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 225       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 226       var next-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 227       var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 228       compare next-cursor-element, 0
 229       break-if-=
 230       copy-object next-cursor-element-ah, cursor-call-path-ah
 231       return
 232     }
 233     # otherwise, move to the next word
 234     var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 235     var next-word/eax: (addr word) <- lookup *next-word-ah
 236     {
 237       compare next-word, 0
 238       break-if-=
 239 #?       print-string 0, "b\n"
 240       cursor-to-start next-word
 241       # . . cursor-word now out of date
 242       var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
 243       increment-final-element cursor-call-path
 244       # Is the new cursor word expanded? If so, it's a function call. Add a
 245       # new level to the cursor-call-path for the call's body.
 246       $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded: {
 247 #?         print-string 0, "c\n"
 248         {
 249           var expanded-words/eax: (addr handle call-path) <- get sandbox, expanded-words
 250           var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 251           compare curr-word-is-expanded?, 0  # false
 252           break-if-= $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded
 253         }
 254         var callee-h: (handle function)
 255         var callee-ah/edx: (addr handle function) <- address callee-h
 256         var functions/ebx: (addr handle function) <- get self, functions
 257         callee functions, next-word, callee-ah
 258         var callee/eax: (addr function) <- lookup *callee-ah
 259         var callee-body-ah/eax: (addr handle line) <- get callee, body
 260         var callee-body/eax: (addr line) <- lookup *callee-body-ah
 261         var callee-body-first-word/edx: (addr handle word) <- get callee-body, data
 262         push-to-call-path-element cursor-call-path, callee-body-first-word
 263         # position cursor at left
 264         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 265         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 266         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 267         var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 268         cursor-to-start cursor-word
 269 #?         print-string 0, "d\n"
 270         return
 271       }
 272     }
 273     return
 274   }
 275   compare key, 0xa  # enter
 276   {
 277     break-if-!=
 278     # toggle display of subsidiary stack
 279     toggle-cursor-word sandbox
 280     return
 281   }
 282   compare key, 0xc  # ctrl-l
 283   $process-sandbox-edit:new-line: {
 284     break-if-!=
 285     # new line in sandbox
 286     append-line sandbox
 287     return
 288   }
 289   # word-based motions
 290   compare key, 2  # ctrl-b
 291   $process-sandbox-edit:prev-word: {
 292     break-if-!=
 293     # jump to previous word at same level
 294     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 295     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 296     {
 297       compare prev-word, 0
 298       break-if-=
 299       cursor-to-end prev-word
 300       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 301       decrement-final-element cursor-call-path
 302       return
 303     }
 304     # if previous word doesn't exist, try to bump up one level
 305     {
 306       var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 307       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 308       var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 309       var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah
 310       compare caller-cursor-element, 0
 311       break-if-=
 312       # check if previous word exists in caller
 313       var caller-word-ah/eax: (addr handle word) <- get caller-cursor-element, word
 314       var caller-word/eax: (addr word) <- lookup *caller-word-ah
 315       var word-before-caller-ah/eax: (addr handle word) <- get caller-word, prev
 316       var word-before-caller/eax: (addr word) <- lookup *word-before-caller-ah
 317       compare word-before-caller, 0
 318       break-if-=
 319       # if so jump to it
 320       drop-from-call-path-element cursor-call-path-ah
 321       decrement-final-element cursor-call-path-ah
 322       return
 323     }
 324   }
 325   compare key, 6  # ctrl-f
 326   $process-sandbox-edit:next-word: {
 327     break-if-!=
 328 #?     print-string 0, "AA\n"
 329     # jump to previous word at same level
 330     var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 331     var next-word/eax: (addr word) <- lookup *next-word-ah
 332     {
 333       compare next-word, 0
 334       break-if-=
 335 #?       print-string 0, "BB\n"
 336       cursor-to-end next-word
 337       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 338       increment-final-element cursor-call-path
 339       return
 340     }
 341     # if next word doesn't exist, try to bump up one level
 342 #?     print-string 0, "CC\n"
 343     var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 344     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 345     var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 346     var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah
 347     compare caller-cursor-element, 0
 348     break-if-=
 349 #?     print-string 0, "DD\n"
 350     copy-object caller-cursor-element-ah, cursor-call-path-ah
 351     return
 352   }
 353   # line-based motions
 354   compare key, 1  # ctrl-a
 355   $process-sandbox-edit:start-of-line: {
 356     break-if-!=
 357     # move cursor up past all calls and to start of line
 358     var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 359     drop-nested-calls cursor-call-path-ah
 360     move-final-element-to-start-of-line cursor-call-path-ah
 361     # move cursor to start of word
 362     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 363     var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 364     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 365     cursor-to-start cursor-word
 366     # this works as long as the first word isn't expanded
 367     # but we don't expect to see zero-arg functions first-up
 368     return
 369   }
 370   compare key, 5  # ctrl-e
 371   $process-sandbox-edit:end-of-line: {
 372     break-if-!=
 373     # move cursor up past all calls and to start of line
 374     var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 375     drop-nested-calls cursor-call-path-ah
 376     move-final-element-to-end-of-line cursor-call-path-ah
 377     # move cursor to end of word
 378     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 379     var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 380     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 381     cursor-to-end cursor-word
 382     # this works because expanded words lie to the right of their bodies
 383     # so the final word is always guaranteed to be at the top-level
 384     return
 385   }
 386   compare key, 0x15  # ctrl-u
 387   $process-sandbox-edit:clear-line: {
 388     break-if-!=
 389     # clear line in sandbox
 390     initialize-sandbox sandbox
 391     return
 392   }
 393   # if cursor is within a call, disable editing hotkeys below
 394   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 395   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 396   var next-cursor-element-ah/eax: (addr handle call-path-element) <- get cursor-call-path, next
 397   var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 398   compare next-cursor-element, 0
 399   {
 400     break-if-=
 401     return
 402   }
 403   # - remaining keys only work at the top row outside any function calls
 404   compare key, 0x7f  # del (backspace on Macs)
 405   $process-sandbox-edit:backspace: {
 406     break-if-!=
 407     # if not at start of some word, delete grapheme before cursor within current word
 408     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 409     compare at-start?, 0  # false
 410     {
 411       break-if-!=
 412       delete-before-cursor cursor-word
 413       return
 414     }
 415     # otherwise delete current word and move to end of prev word
 416     var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 417     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 418     {
 419       compare prev-word, 0
 420       break-if-=
 421       cursor-to-end prev-word
 422       delete-next prev-word
 423       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 424       decrement-final-element cursor-call-path
 425     }
 426     return
 427   }
 428   compare key, 0x20  # space
 429   $process-sandbox-edit:space: {
 430     break-if-!=
 431 #?     print-string 0, "space\n"
 432     # if cursor is at start of word, insert word before
 433     {
 434       var at-start?/eax: boolean <- cursor-at-start? cursor-word
 435       compare at-start?, 0  # false
 436       break-if-=
 437       var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 438       append-word prev-word-ah
 439       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 440       decrement-final-element cursor-call-path
 441       return
 442     }
 443     # if start of word is quote and grapheme before cursor is not, just insert it as usual
 444     # TODO: support string escaping
 445     {
 446       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 447       compare first-grapheme, 0x22  # double quote
 448       break-if-!=
 449       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 450       compare final-grapheme, 0x22  # double quote
 451       break-if-=
 452       break $process-sandbox-edit:space
 453     }
 454     # if start of word is '[' and grapheme before cursor is not ']', just insert it as usual
 455     # TODO: support nested arrays
 456     {
 457       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 458       compare first-grapheme, 0x5b  # '['
 459       break-if-!=
 460       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 461       compare final-grapheme, 0x5d  # ']'
 462       break-if-=
 463       break $process-sandbox-edit:space
 464     }
 465     # otherwise insert word after and move cursor to it for the next key
 466     # (but we'll continue to track the current cursor-word for the rest of this function)
 467     append-word cursor-word-ah
 468     var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 469     increment-final-element cursor-call-path
 470     # if cursor is at end of word, that's all
 471     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 472     compare at-end?, 0  # false
 473     {
 474       break-if-=
 475       return
 476     }
 477     # otherwise we're in the middle of a word
 478     # move everything after cursor to the (just created) next word
 479     var next-word-ah/eax: (addr handle word) <- get cursor-word, next
 480     var _next-word/eax: (addr word) <- lookup *next-word-ah
 481     var next-word/ebx: (addr word) <- copy _next-word
 482     {
 483       var at-end?/eax: boolean <- cursor-at-end? cursor-word
 484       compare at-end?, 0  # false
 485       break-if-!=
 486       var g/eax: grapheme <- pop-after-cursor cursor-word
 487       add-grapheme-to-word next-word, g
 488       loop
 489     }
 490     cursor-to-start next-word
 491     return
 492   }
 493   compare key, 0xe  # ctrl-n
 494   $process:rename-word: {
 495     break-if-!=
 496     # TODO: ensure current word is not a function
 497     # rename word at cursor
 498     var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-cursor-word
 499     allocate new-name-ah
 500     var new-name/eax: (addr word) <- lookup *new-name-ah
 501     initialize-word new-name
 502     return
 503   }
 504   compare key, 4  # ctrl-d
 505   $process:define-function: {
 506     break-if-!=
 507     # define function out of line at cursor
 508     var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-function
 509     allocate new-name-ah
 510     var new-name/eax: (addr word) <- lookup *new-name-ah
 511     initialize-word new-name
 512     return
 513   }
 514   # otherwise insert key within current word
 515   var g/edx: grapheme <- copy key
 516   var print?/eax: boolean <- real-grapheme? key
 517   $process-sandbox-edit:real-grapheme: {
 518     compare print?, 0  # false
 519     break-if-=
 520     add-grapheme-to-word cursor-word, g
 521     return
 522   }
 523   # silently ignore other hotkeys
 524 }
 525 
 526 # collect new name in partial-name-for-cursor-word, and then rename the word
 527 # at cursor to it
 528 # Precondition: cursor-call-path is a singleton (not within a call)
 529 fn process-sandbox-rename _sandbox: (addr sandbox), key: grapheme {
 530   var sandbox/esi: (addr sandbox) <- copy _sandbox
 531   var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-cursor-word
 532   # if 'esc' pressed, cancel rename
 533   compare key, 0x1b  # esc
 534   $process-sandbox-rename:cancel: {
 535     break-if-!=
 536     clear-object new-name-ah
 537     return
 538   }
 539   # if 'enter' pressed, perform rename
 540   compare key, 0xa  # enter
 541   $process-sandbox-rename:commit: {
 542     break-if-!=
 543 #?     print-string 0, "rename\n"
 544     # new line
 545     var new-line-h: (handle line)
 546     var new-line-ah/eax: (addr handle line) <- address new-line-h
 547     allocate new-line-ah
 548     var new-line/eax: (addr line) <- lookup *new-line-ah
 549     initialize-line new-line
 550     var new-line-word-ah/ecx: (addr handle word) <- get new-line, data
 551     {
 552       # move word at cursor to new line
 553       var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 554       var cursor/eax: (addr call-path-element) <- lookup *cursor-ah
 555       var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word
 556 #?       print-string 0, "cursor before at word "
 557 #?       {
 558 #?         var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 559 #?         print-word 0, cursor-word
 560 #?         print-string 0, "\n"
 561 #?       }
 562       move-word-contents word-at-cursor-ah, new-line-word-ah
 563       # copy name to word at cursor
 564       copy-word-contents-before-cursor new-name-ah, word-at-cursor-ah
 565 #?       print-string 0, "cursor after at word "
 566 #?       {
 567 #?         var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 568 #?         print-word 0, cursor-word
 569 #?         print-string 0, "\n"
 570 #?         var foo/eax: int <- copy cursor-word
 571 #?         print-int32-hex 0, foo
 572 #?         print-string 0, "\n"
 573 #?       }
 574 #?       print-string 0, "new name word "
 575 #?       {
 576 #?         var new-name/eax: (addr word) <- lookup *new-name-ah
 577 #?         print-word 0, new-name
 578 #?         print-string 0, "\n"
 579 #?         var foo/eax: int <- copy new-name
 580 #?         print-int32-hex 0, foo
 581 #?         print-string 0, "\n"
 582 #?       }
 583     }
 584     # prepend '=' to name
 585     {
 586       var new-name/eax: (addr word) <- lookup *new-name-ah
 587       cursor-to-start new-name
 588       add-grapheme-to-word new-name, 0x3d  # '='
 589     }
 590     # append name to new line
 591     chain-words new-line-word-ah, new-name-ah
 592     # new-line->next = sandbox->data
 593     var new-line-next/ecx: (addr handle line) <- get new-line, next
 594     var sandbox-slot/edx: (addr handle line) <- get sandbox, data
 595     copy-object sandbox-slot, new-line-next
 596     # sandbox->data = new-line
 597     copy-handle new-line-h, sandbox-slot
 598     # clear partial-name-for-cursor-word
 599     clear-object new-name-ah
 600 #?     var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 601 #?     var cursor/eax: (addr call-path-element) <- lookup *cursor-ah
 602 #?     var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word
 603 #?     print-string 0, "cursor after rename: "
 604 #?     {
 605 #?       var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 606 #?       print-word 0, cursor-word
 607 #?       print-string 0, " -- "
 608 #?       var foo/eax: int <- copy cursor-word
 609 #?       print-int32-hex 0, foo
 610 #?       print-string 0, "\n"
 611 #?     }
 612     return
 613   }
 614   #
 615   compare key, 0x7f  # del (backspace on Macs)
 616   $process-sandbox-rename:backspace: {
 617     break-if-!=
 618     # if not at start, delete grapheme before cursor
 619     var new-name/eax: (addr word) <- lookup *new-name-ah
 620     var at-start?/eax: boolean <- cursor-at-start? new-name
 621     compare at-start?, 0  # false
 622     {
 623       break-if-!=
 624       var new-name/eax: (addr word) <- lookup *new-name-ah
 625       delete-before-cursor new-name
 626     }
 627     return
 628   }
 629   # otherwise insert key within current word
 630   var print?/eax: boolean <- real-grapheme? key
 631   $process-sandbox-rename:real-grapheme: {
 632     compare print?, 0  # false
 633     break-if-=
 634     var new-name/eax: (addr word) <- lookup *new-name-ah
 635     add-grapheme-to-word new-name, key
 636     return
 637   }
 638   # silently ignore other hotkeys
 639 }
 640 
 641 # collect new name in partial-name-for-function, and then define the last line
 642 # of the sandbox to be a new function with that name. Replace the last line
 643 # with a call to the appropriate function.
 644 # Precondition: cursor-call-path is a singleton (not within a call)
 645 fn process-sandbox-define _sandbox: (addr sandbox), functions: (addr handle function), key: grapheme {
 646   var sandbox/esi: (addr sandbox) <- copy _sandbox
 647   var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-function
 648   # if 'esc' pressed, cancel define
 649   compare key, 0x1b  # esc
 650   $process-sandbox-define:cancel: {
 651     break-if-!=
 652     clear-object new-name-ah
 653     return
 654   }
 655   # if 'enter' pressed, perform define
 656   compare key, 0xa  # enter
 657   $process-sandbox-define:commit: {
 658     break-if-!=
 659 #?     print-string 0, "define\n"
 660     # create new function
 661     var new-function: (handle function)
 662     var new-function-ah/ecx: (addr handle function) <- address new-function
 663     allocate new-function-ah
 664     var _new-function/eax: (addr function) <- lookup *new-function-ah
 665     var new-function/ebx: (addr function) <- copy _new-function
 666     var dest/edx: (addr handle function) <- get new-function, next
 667     copy-object functions, dest
 668     copy-object new-function-ah, functions
 669     # set function name to new-name
 670     var new-name/eax: (addr word) <- lookup *new-name-ah
 671     var dest/edx: (addr handle array byte) <- get new-function, name
 672     word-to-string new-name, dest
 673     # move final line to body
 674     var body-ah/eax: (addr handle line) <- get new-function, body
 675     allocate body-ah
 676     var body/eax: (addr line) <- lookup *body-ah
 677     var body-contents/ecx: (addr handle word) <- get body, data
 678     var final-line-storage: (handle line)
 679     var final-line-ah/eax: (addr handle line) <- address final-line-storage
 680     final-line sandbox, final-line-ah
 681     var final-line/eax: (addr line) <- lookup *final-line-ah
 682     var final-line-contents/eax: (addr handle word) <- get final-line, data
 683     copy-object final-line-contents, body-contents
 684     #
 685     copy-unbound-words-to-args functions
 686     #
 687     var empty-word: (handle word)
 688     copy-handle empty-word, final-line-contents
 689     construct-call functions, final-line-contents
 690     # clear partial-name-for-function
 691     var empty-word: (handle word)
 692     copy-handle empty-word, new-name-ah
 693     # update cursor
 694     var final-line/eax: (addr line) <- lookup final-line-storage
 695     var cursor-call-path-ah/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
 696     allocate cursor-call-path-ah  # leak
 697     initialize-path-from-line final-line, cursor-call-path-ah
 698     return
 699   }
 700   #
 701   compare key, 0x7f  # del (backspace on Macs)
 702   $process-sandbox-define:backspace: {
 703     break-if-!=
 704     # if not at start, delete grapheme before cursor
 705     var new-name/eax: (addr word) <- lookup *new-name-ah
 706     var at-start?/eax: boolean <- cursor-at-start? new-name
 707     compare at-start?, 0  # false
 708     {
 709       break-if-!=
 710       var new-name/eax: (addr word) <- lookup *new-name-ah
 711       delete-before-cursor new-name
 712     }
 713     return
 714   }
 715   # otherwise insert key within current word
 716   var print?/eax: boolean <- real-grapheme? key
 717   $process-sandbox-define:real-grapheme: {
 718     compare print?, 0  # false
 719     break-if-=
 720     var new-name/eax: (addr word) <- lookup *new-name-ah
 721     add-grapheme-to-word new-name, key
 722     return
 723   }
 724   # silently ignore other hotkeys
 725 }
 726 
 727 # extract from the body of the first function in 'functions' all words that
 728 # aren't defined in the rest of 'functions'. Prepend them in reverse order.
 729 # Assumes function body is a single line for now.
 730 fn copy-unbound-words-to-args _functions: (addr handle function) {
 731   # target
 732   var target-ah/eax: (addr handle function) <- copy _functions
 733   var _target/eax: (addr function) <- lookup *target-ah
 734   var target/esi: (addr function) <- copy _target
 735   var dest-ah/edi: (addr handle word) <- get target, args
 736   # next
 737   var functions-ah/edx: (addr handle function) <- get target, next
 738   # src
 739   var line-ah/eax: (addr handle line) <- get target, body
 740   var line/eax: (addr line) <- lookup *line-ah
 741   var curr-ah/eax: (addr handle word) <- get line, data
 742   var curr/eax: (addr word) <- lookup *curr-ah
 743   {
 744     compare curr, 0
 745     break-if-=
 746     $copy-unbound-words-to-args:loop-iter: {
 747       # is it a number?
 748       {
 749         var is-int?/eax: boolean <- word-is-decimal-integer? curr
 750         compare is-int?, 0  # false
 751         break-if-!= $copy-unbound-words-to-args:loop-iter
 752       }
 753       # is it a pre-existing function?
 754       var bound?/ebx: boolean <- bound-function? curr, functions-ah
 755       compare bound?, 0  # false
 756       break-if-!=
 757       # is it already bound as an arg?
 758       var dup?/ebx: boolean <- arg-exists? _functions, curr  # _functions = target-ah
 759       compare dup?, 0  # false
 760       break-if-!= $copy-unbound-words-to-args:loop-iter
 761       # push copy of curr before dest-ah
 762       var rest-h: (handle word)
 763       var rest-ah/ecx: (addr handle word) <- address rest-h
 764       copy-object dest-ah, rest-ah
 765       copy-word curr, dest-ah
 766       chain-words dest-ah, rest-ah
 767     }
 768     var next-ah/ecx: (addr handle word) <- get curr, next
 769     curr <- lookup *next-ah
 770     loop
 771   }
 772 }
 773 
 774 fn bound-function? w: (addr word), functions-ah: (addr handle function) -> _/ebx: boolean {
 775   var result/ebx: boolean <- copy 1  # true
 776   {
 777     ## numbers
 778     # if w == "+" return true
 779     var subresult/eax: boolean <- word-equal? w, "+"
 780     compare subresult, 0  # false
 781     break-if-!=
 782     # if w == "-" return true
 783     subresult <- word-equal? w, "-"
 784     compare subresult, 0  # false
 785     break-if-!=
 786     # if w == "*" return true
 787     subresult <- word-equal? w, "*"
 788     compare subresult, 0  # false
 789     break-if-!=
 790     # if w == "/" return true
 791     subresult <- word-equal? w, "/"
 792     compare subresult, 0  # false
 793     break-if-!=
 794     # if w == "sqrt" return true
 795     subresult <- word-equal? w, "sqrt"
 796     compare subresult, 0  # false
 797     break-if-!=
 798     ## strings/arrays
 799     # if w == "len" return true
 800     subresult <- word-equal? w, "len"
 801     compare subresult, 0  # false
 802     break-if-!=
 803     ## files
 804     # if w == "open" return true
 805     subresult <- word-equal? w, "open"
 806     compare subresult, 0  # false
 807     break-if-!=
 808     # if w == "read" return true
 809     subresult <- word-equal? w, "read"
 810     compare subresult, 0  # false
 811     break-if-!=
 812     # if w == "slurp" return true
 813     subresult <- word-equal? w, "slurp"
 814     compare subresult, 0  # false
 815     break-if-!=
 816     # if w == "lines" return true
 817     subresult <- word-equal? w, "lines"
 818     compare subresult, 0  # false
 819     break-if-!=
 820     ## screens
 821     # if w == "fake-screen" return true
 822     subresult <- word-equal? w, "fake-screen"
 823     compare subresult, 0  # false
 824     break-if-!=
 825     # if w == "print" return true
 826     subresult <- word-equal? w, "print"
 827     compare subresult, 0  # false
 828     break-if-!=
 829     # if w == "move" return true
 830     subresult <- word-equal? w, "move"
 831     compare subresult, 0  # false
 832     break-if-!=
 833     # if w == "up" return true
 834     subresult <- word-equal? w, "up"
 835     compare subresult, 0  # false
 836     break-if-!=
 837     # if w == "down" return true
 838     subresult <- word-equal? w, "down"
 839     compare subresult, 0  # false
 840     break-if-!=
 841     # if w == "left" return true
 842     subresult <- word-equal? w, "left"
 843     compare subresult, 0  # false
 844     break-if-!=
 845     # if w == "right" return true
 846     subresult <- word-equal? w, "right"
 847     compare subresult, 0  # false
 848     break-if-!=
 849     ## hacks
 850     # if w == "dup" return true
 851     subresult <- word-equal? w, "dup"
 852     compare subresult, 0  # false
 853     break-if-!=
 854     # if w == "swap" return true
 855     subresult <- word-equal? w, "swap"
 856     compare subresult, 0  # false
 857     break-if-!=
 858     # return w in functions
 859     var out-h: (handle function)
 860     var out/eax: (addr handle function) <- address out-h
 861     callee functions-ah, w, out
 862     var found?/eax: (addr function) <- lookup *out
 863     result <- copy found?
 864   }
 865   return result
 866 }
 867 
 868 fn arg-exists? _f-ah: (addr handle function), arg: (addr word) -> _/ebx: boolean {
 869   var f-ah/eax: (addr handle function) <- copy _f-ah
 870   var f/eax: (addr function) <- lookup *f-ah
 871   var args-ah/eax: (addr handle word) <- get f, args
 872   var result/ebx: boolean <- word-exists? args-ah, arg
 873   return result
 874 }
 875 
 876 # construct a call to `f` with copies of exactly its args
 877 fn construct-call _f-ah: (addr handle function), _dest-ah: (addr handle word) {
 878   var f-ah/eax: (addr handle function) <- copy _f-ah
 879   var _f/eax: (addr function) <- lookup *f-ah
 880   var f/esi: (addr function) <- copy _f
 881   # append args in reverse
 882   var args-ah/eax: (addr handle word) <- get f, args
 883   var dest-ah/edi: (addr handle word) <- copy _dest-ah
 884   copy-words-in-reverse args-ah, dest-ah
 885   # append name
 886   var name-ah/eax: (addr handle array byte) <- get f, name
 887   var name/eax: (addr array byte) <- lookup *name-ah
 888   append-word-at-end-with dest-ah, name
 889 }
 890 
 891 fn word-index _words: (addr handle word), _n: int, out: (addr handle word) {
 892   var n/ecx: int <- copy _n
 893   {
 894     compare n, 0
 895     break-if-!=
 896     copy-object _words, out
 897     return
 898   }
 899   var words-ah/eax: (addr handle word) <- copy _words
 900   var words/eax: (addr word) <- lookup *words-ah
 901   var next/eax: (addr handle word) <- get words, next
 902   n <- decrement
 903   word-index next, n, out
 904 }
 905 
 906 fn toggle-cursor-word _sandbox: (addr sandbox) {
 907   var sandbox/esi: (addr sandbox) <- copy _sandbox
 908   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
 909   var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
 910 #?   print-string 0, "cursor call path: "
 911 #?   dump-call-path-element 0, cursor-call-path
 912 #?   print-string 0, "expanded words:\n"
 913 #?   dump-call-paths 0, expanded-words
 914   var already-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 915   compare already-expanded?, 0  # false
 916   {
 917     break-if-!=
 918 #?     print-string 0, "expand\n"
 919     # if not already-expanded, insert
 920     insert-in-call-path expanded-words cursor-call-path
 921 #?     print-string 0, "expanded words now:\n"
 922 #?     dump-call-paths 0, expanded-words
 923     return
 924   }
 925   {
 926     break-if-=
 927     # otherwise delete
 928     delete-in-call-path expanded-words cursor-call-path
 929   }
 930 }
 931 
 932 fn append-line _sandbox: (addr sandbox) {
 933   var sandbox/esi: (addr sandbox) <- copy _sandbox
 934   var line-ah/ecx: (addr handle line) <- get sandbox, data
 935   {
 936     var line/eax: (addr line) <- lookup *line-ah
 937     var next-line-ah/edx: (addr handle line) <- get line, next
 938     var next-line/eax: (addr line) <- lookup *next-line-ah
 939     compare next-line, 0
 940     break-if-=
 941     line-ah <- copy next-line-ah
 942     loop
 943   }
 944   var line/eax: (addr line) <- lookup *line-ah
 945   var final-line-ah/edx: (addr handle line) <- get line, next
 946   allocate final-line-ah
 947   var final-line/eax: (addr line) <- lookup *final-line-ah
 948   initialize-line final-line
 949   var final-prev/eax: (addr handle line) <- get final-line, prev
 950   copy-object line-ah, final-prev
 951   # clear cursor
 952   var final-line/eax: (addr line) <- lookup *final-line-ah
 953   var word-ah/ecx: (addr handle word) <- get final-line, data
 954   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 955   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 956   var dest/eax: (addr handle word) <- get cursor-call-path, word
 957   copy-object word-ah, dest
 958 }
 959 
 960 #############
 961 # Visualize
 962 #############
 963 
 964 fn evaluate-environment _env: (addr environment), stack: (addr value-stack) {
 965   var env/esi: (addr environment) <- copy _env
 966   # functions
 967   var functions/edx: (addr handle function) <- get env, functions
 968   # line
 969   var sandbox-ah/esi: (addr handle sandbox) <- get env, sandboxes
 970   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
 971   var line-ah/eax: (addr handle line) <- get sandbox, data
 972   var _line/eax: (addr line) <- lookup *line-ah
 973   var line/esi: (addr line) <- copy _line
 974   evaluate functions, 0, line, 0, stack
 975 }
 976 
 977 fn render _env: (addr environment) {
 978 #?   print-string 0, "== render\n"
 979   var env/esi: (addr environment) <- copy _env
 980   clear-canvas env
 981   # screen
 982   var screen-ah/eax: (addr handle screen) <- get env, screen
 983   var _screen/eax: (addr screen) <- lookup *screen-ah
 984   var screen/edi: (addr screen) <- copy _screen
 985   # repl-col
 986   var _repl-col/eax: (addr int) <- get env, code-separator-col
 987   var repl-col/ecx: int <- copy *_repl-col
 988   repl-col <- add 2  # repl-margin-left
 989   # functions
 990   var functions/edx: (addr handle function) <- get env, functions
 991   # sandbox
 992   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
 993   var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah
 994   # bindings
 995   var bindings-storage: table
 996   var bindings/ebx: (addr table) <- address bindings-storage
 997   initialize-table bindings, 0x10
 998 #?   print-string 0, "render-sandbox {\n"
 999   render-sandbox screen, functions, bindings, cursor-sandbox, 3, repl-col
1000 #?   print-string 0, "render-sandbox }\n"
1001 }
1002 
1003 fn render-sandbox screen: (addr screen), functions: (addr handle function), bindings: (addr table), _sandbox: (addr sandbox), top-row: int, left-col: int {
1004   var sandbox/esi: (addr sandbox) <- copy _sandbox
1005   # line
1006   var curr-line-ah/eax: (addr handle line) <- get sandbox, data
1007   var _curr-line/eax: (addr line) <- lookup *curr-line-ah
1008   var curr-line/ecx: (addr line) <- copy _curr-line
1009   #
1010   var curr-row/edx: int <- copy top-row
1011   # cursor row, col
1012   var cursor-row: int
1013   var cursor-row-addr: (addr int)
1014   var tmp/eax: (addr int) <- address cursor-row
1015   copy-to cursor-row-addr, tmp
1016   var cursor-col: int
1017   var cursor-col-addr: (addr int)
1018   tmp <- address cursor-col
1019   copy-to cursor-col-addr, tmp
1020   # render all but final line without stack
1021 #?   print-string 0, "render all but final line\n"
1022   {
1023     var next-line-ah/eax: (addr handle line) <- get curr-line, next
1024     var next-line/eax: (addr line) <- lookup *next-line-ah
1025     compare next-line, 0
1026     break-if-=
1027     {
1028       var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1029       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1030       var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1031       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1032       # it's enough to pass in the first word of the path, because if the path isn't a singleton the word is guaranteed to be unique
1033       render-line-without-stack screen, curr-line, curr-row, left-col, cursor-word, cursor-row-addr, cursor-col-addr
1034     }
1035     curr-line <- copy next-line
1036     curr-row <- add 2
1037     loop
1038   }
1039   #
1040   render-final-line-with-stack screen, functions, bindings, sandbox, curr-row, left-col, cursor-row-addr, cursor-col-addr
1041   # at most one of the following dialogs will be rendered
1042   render-rename-dialog screen, sandbox, cursor-row, cursor-col
1043   render-define-dialog screen, sandbox, cursor-row, cursor-col
1044   move-cursor screen, cursor-row, cursor-col
1045 }
1046 
1047 fn render-final-line-with-stack screen: (addr screen), functions: (addr handle function), bindings: (addr table), _sandbox: (addr sandbox), top-row: int, left-col: int, cursor-row-addr: (addr int), cursor-col-addr: (addr int) {
1048   var sandbox/esi: (addr sandbox) <- copy _sandbox
1049   # expanded-words
1050   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
1051   # cursor-word
1052   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1053   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1054   var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1055   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1056   var cursor-word/ebx: (addr word) <- copy _cursor-word
1057 #?   print-string 0, "word at cursor: "
1058 #?   print-word 0, cursor-word
1059 #?   print-string 0, "\n"
1060   # cursor-call-path
1061   var cursor-call-path: (addr handle call-path-element)
1062   {
1063     var src/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1064     copy-to cursor-call-path, src
1065   }
1066   # first line
1067   var first-line-ah/eax: (addr handle line) <- get sandbox, data
1068   var _first-line/eax: (addr line) <- lookup *first-line-ah
1069   var first-line/edx: (addr line) <- copy _first-line
1070   # final line
1071   var final-line-storage: (handle line)
1072   var final-line-ah/eax: (addr handle line) <- address final-line-storage
1073   final-line sandbox, final-line-ah
1074   var final-line/eax: (addr line) <- lookup *final-line-ah
1075   # curr-path
1076   var curr-path-storage: (handle call-path-element)
1077   var curr-path/ecx: (addr handle call-path-element) <- address curr-path-storage
1078   allocate curr-path  # leak
1079   initialize-path-from-line final-line, curr-path
1080   #
1081   var dummy/ecx: int <- render-line screen, functions, bindings, first-line, final-line, expanded-words, top-row, left-col, curr-path, cursor-word, cursor-call-path, cursor-row-addr, cursor-col-addr
1082 }
1083 
1084 fn final-line _sandbox: (addr sandbox), out: (addr handle line) {
1085   var sandbox/esi: (addr sandbox) <- copy _sandbox
1086   var curr-line-ah/ecx: (addr handle line) <- get sandbox, data
1087   {
1088     var curr-line/eax: (addr line) <- lookup *curr-line-ah
1089     var next-line-ah/edx: (addr handle line) <- get curr-line, next
1090     var next-line/eax: (addr line) <- lookup *next-line-ah
1091     compare next-line, 0
1092     break-if-=
1093     curr-line-ah <- copy next-line-ah
1094     loop
1095   }
1096   copy-object curr-line-ah, out
1097 }
1098 
1099 fn render-rename-dialog screen: (addr screen), _sandbox: (addr sandbox), cursor-row: int, cursor-col: int {
1100   var sandbox/edi: (addr sandbox) <- copy _sandbox
1101   var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1102   var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah?
1103   compare rename-word-mode?, 0
1104   break-if-=
1105   # clear a space for the dialog
1106   var top-row/eax: int <- copy cursor-row
1107   top-row <- subtract 3
1108   var bottom-row/ecx: int <- copy cursor-row
1109   bottom-row <- add 3
1110   var left-col/edx: int <- copy cursor-col
1111   left-col <- subtract 0x10
1112   var right-col/ebx: int <- copy cursor-col
1113   right-col <- add 0x10
1114   clear-rect screen, top-row, left-col, bottom-row, right-col
1115   draw-box screen, top-row, left-col, bottom-row, right-col
1116   # render a little menu for the dialog
1117   var menu-row/ecx: int <- copy bottom-row
1118   menu-row <- decrement
1119   var menu-col/edx: int <- copy left-col
1120   menu-col <- add 2
1121   move-cursor screen, menu-row, menu-col
1122   start-reverse-video screen
1123   print-string screen, " esc "
1124   reset-formatting screen
1125   print-string screen, " cancel  "
1126   start-reverse-video screen
1127   print-string screen, " enter "
1128   reset-formatting screen
1129   print-string screen, " rename  "
1130   # draw the word, positioned appropriately around the cursor
1131   var start-col/ecx: int <- copy cursor-col
1132   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1133   var word/eax: (addr word) <- lookup *word-ah?
1134   var cursor-index/eax: int <- cursor-index word
1135   start-col <- subtract cursor-index
1136   move-cursor screen, cursor-row, start-col
1137   var word/eax: (addr word) <- lookup *word-ah?
1138   print-word screen, word
1139 }
1140 
1141 fn render-define-dialog screen: (addr screen), _sandbox: (addr sandbox), cursor-row: int, cursor-col: int {
1142   var sandbox/edi: (addr sandbox) <- copy _sandbox
1143   var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function
1144   var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah?
1145   compare define-function-mode?, 0
1146   break-if-=
1147   # clear a space for the dialog
1148   var top-row/eax: int <- copy cursor-row
1149   top-row <- subtract 3
1150   var bottom-row/ecx: int <- copy cursor-row
1151   bottom-row <- add 3
1152   var left-col/edx: int <- copy cursor-col
1153   left-col <- subtract 0x10
1154   var right-col/ebx: int <- copy cursor-col
1155   right-col <- add 0x10
1156   clear-rect screen, top-row, left-col, bottom-row, right-col
1157   draw-box screen, top-row, left-col, bottom-row, right-col
1158   # render a little menu for the dialog
1159   var menu-row/ecx: int <- copy bottom-row
1160   menu-row <- decrement
1161   var menu-col/edx: int <- copy left-col
1162   menu-col <- add 2
1163   move-cursor screen, menu-row, menu-col
1164   start-reverse-video screen
1165   print-string screen, " esc "
1166   reset-formatting screen
1167   print-string screen, " cancel  "
1168   start-reverse-video screen
1169   print-string screen, " enter "
1170   reset-formatting screen
1171   print-string screen, " define  "
1172   # draw the word, positioned appropriately around the cursor
1173   var start-col/ecx: int <- copy cursor-col
1174   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-function
1175   var word/eax: (addr word) <- lookup *word-ah?
1176   var cursor-index/eax: int <- cursor-index word
1177   start-col <- subtract cursor-index
1178   move-cursor screen, cursor-row, start-col
1179   var word/eax: (addr word) <- lookup *word-ah?
1180   print-word screen, word
1181 }
1182 
1183 # Render just the words in 'line'.
1184 fn render-line-without-stack screen: (addr screen), _line: (addr line), curr-row: int, left-col: int, cursor-word: (addr word), cursor-row-addr: (addr int), cursor-col-addr: (addr int) {
1185   # curr-word
1186   var line/eax: (addr line) <- copy _line
1187   var first-word-ah/eax: (addr handle word) <- get line, data
1188   var _curr-word/eax: (addr word) <- lookup *first-word-ah
1189   var curr-word/esi: (addr word) <- copy _curr-word
1190   #
1191   # loop-carried dependency
1192   var curr-col/ecx: int <- copy left-col
1193   #
1194   {
1195     compare curr-word, 0
1196     break-if-=
1197 #?     print-string 0, "-- word in penultimate lines: "
1198 #?     {
1199 #?       var foo/eax: int <- copy curr-word
1200 #?       print-int32-hex 0, foo
1201 #?     }
1202 #?     print-string 0, "\n"
1203     var old-col/edx: int <- copy curr-col
1204     reset-formatting screen
1205     move-cursor screen, curr-row, curr-col
1206     print-word screen, curr-word
1207     {
1208       var max-width/eax: int <- word-length curr-word
1209       curr-col <- add max-width
1210       curr-col <- add 1  # margin-right
1211     }
1212     # cache cursor column if necessary
1213     {
1214       compare curr-word, cursor-word
1215       break-if-!=
1216 #?       print-string 0, "Cursor at "
1217 #?       print-int32-decimal 0, curr-row
1218 #?       print-string 0, ", "
1219 #?       print-int32-decimal 0, old-col
1220 #?       print-string 0, "\n"
1221 #?       print-string 0, "contents: "
1222 #?       print-word 0, cursor-word
1223 #?       print-string 0, "\n"
1224 #?       {
1225 #?         var foo/eax: int <- copy cursor-word
1226 #?         print-int32-hex 0, foo
1227 #?         print-string 0, "\n"
1228 #?       }
1229       var dest/ecx: (addr int) <- copy cursor-row-addr
1230       var src/eax: int <- copy curr-row
1231       copy-to *dest, src
1232       dest <- copy cursor-col-addr
1233       copy-to *dest, old-col
1234       var cursor-index-in-word/eax: int <- cursor-index curr-word
1235       add-to *dest, cursor-index-in-word
1236     }
1237     # loop update
1238     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1239     var _curr-word/eax: (addr word) <- lookup *next-word-ah
1240     curr-word <- copy _curr-word
1241     loop
1242   }
1243 }
1244 
1245 fn call-depth-at-cursor _sandbox: (addr sandbox) -> _/eax: int {
1246   var sandbox/esi: (addr sandbox) <- copy _sandbox
1247   var cursor-call-path/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
1248   var result/eax: int <- call-path-element-length cursor-call-path
1249   result <- add 2  # input-row - 1
1250   return result
1251 }
1252 
1253 fn call-path-element-length _x: (addr handle call-path-element) -> _/eax: int {
1254   var curr-ah/ecx: (addr handle call-path-element) <- copy _x
1255   var result/edi: int <- copy 0
1256   {
1257     var curr/eax: (addr call-path-element) <- lookup *curr-ah
1258     compare curr, 0
1259     break-if-=
1260     curr-ah <- get curr, next
1261     result <- increment
1262     loop
1263   }
1264   return result
1265 }
1266 
1267 # Render the line of words in line, along with the state of the stack under each word.
1268 # Also render any expanded function calls using recursive calls.
1269 #
1270 # Along the way, compute the column the cursor should be positioned at (cursor-col-addr).
1271 fn render-line screen: (addr screen), functions: (addr handle function), bindings: (addr table), first-line: (addr line), _line: (addr line), expanded-words: (addr handle call-path), top-row: int, left-col: int, curr-path: (addr handle call-path-element), cursor-word: (addr word), cursor-call-path: (addr handle call-path-element), cursor-row-addr: (addr int), cursor-col-addr: (addr int) -> _/ecx: int {
1272 #?   print-string 0, "render-line\n"
1273 #?   dump-table bindings
1274   # curr-word
1275   var line/esi: (addr line) <- copy _line
1276   var first-word-ah/eax: (addr handle word) <- get line, data
1277   var curr-word/eax: (addr word) <- lookup *first-word-ah
1278   #
1279   # loop-carried dependency
1280   var curr-col/ecx: int <- copy left-col
1281   #
1282   {
1283     compare curr-word, 0
1284     break-if-=
1285 #?     print-string 0, "-- word "
1286 #?     print-word 0, curr-word
1287 #?     print-string 0, "\n"
1288     # if necessary, first render columns for subsidiary stack
1289     $render-line:subsidiary: {
1290       {
1291 #?         print-string 0, "check sub\n"
1292         var display-subsidiary-stack?/eax: boolean <- find-in-call-paths expanded-words, curr-path
1293         compare display-subsidiary-stack?, 0  # false
1294         break-if-= $render-line:subsidiary
1295       }
1296 #?       print-string 0, "render subsidiary stack\n"
1297       # does function exist?
1298       var callee/edi: (addr function) <- copy 0
1299       {
1300         var callee-h: (handle function)
1301         var callee-ah/ecx: (addr handle function) <- address callee-h
1302         callee functions, curr-word, callee-ah
1303         var _callee/eax: (addr function) <- lookup *callee-ah
1304         callee <- copy _callee
1305         compare callee, 0
1306         break-if-= $render-line:subsidiary
1307       }
1308       move-cursor screen, top-row, curr-col
1309       start-color screen, 8, 7
1310       print-word screen, curr-word
1311       {
1312         var word-len/eax: int <- word-length curr-word
1313         curr-col <- add word-len
1314         curr-col <- add 2
1315         increment top-row
1316       }
1317       # obtain stack at call site
1318       var stack-storage: value-stack
1319       var stack/edx: (addr value-stack) <- address stack-storage
1320       initialize-value-stack stack, 0x10
1321       {
1322         var prev-word-ah/eax: (addr handle word) <- get curr-word, prev
1323         var prev-word/eax: (addr word) <- lookup *prev-word-ah
1324         compare prev-word, 0
1325         break-if-=
1326         var bindings2-storage: table
1327         var bindings2/ebx: (addr table) <- address bindings2-storage
1328         deep-copy-table bindings, bindings2
1329         evaluate functions, bindings2, first-line, prev-word, stack
1330       }
1331       # construct new bindings
1332       var callee-bindings-storage: table
1333       var callee-bindings/esi: (addr table) <- address callee-bindings-storage
1334       initialize-table callee-bindings, 0x10
1335       bind-args callee, stack, callee-bindings
1336       # obtain body
1337       var callee-body-ah/eax: (addr handle line) <- get callee, body
1338       var callee-body/eax: (addr line) <- lookup *callee-body-ah
1339       var callee-body-first-word/edx: (addr handle word) <- get callee-body, data
1340       # - render subsidiary stack
1341       push-to-call-path-element curr-path, callee-body-first-word  # leak
1342 #?       print-string 0, "subsidiary {\n"
1343 #?       dump-table callee-bindings
1344 #?       syscall_exit
1345       curr-col <- render-line screen, functions, callee-bindings, callee-body, callee-body, expanded-words, top-row, curr-col, curr-path, cursor-word, cursor-call-path, cursor-row-addr, cursor-col-addr
1346 #?       print-string 0, "}\n"
1347       drop-from-call-path-element curr-path
1348       #
1349       move-cursor screen, top-row, curr-col
1350       print-code-point screen, 0x21d7  # ⇗
1351       #
1352       curr-col <- add 2
1353       decrement top-row
1354     }
1355     # render main column
1356     var old-col/edx: int <- copy curr-col
1357     var bindings2-storage: table
1358     var bindings2/ebx: (addr table) <- address bindings2-storage
1359 #?     print-string 0, "deep-copy {\n"
1360     deep-copy-table bindings, bindings2
1361 #?     print-string 0, "}\n"
1362 #?     print-string 0, "render column {\n"
1363     curr-col <- render-column screen, functions, bindings2, first-line, line, curr-word, top-row, curr-col
1364 #?     print-string 0, "}\n"
1365     # cache cursor column if necessary
1366     $render-line:cache-cursor-column: {
1367       {
1368         var found?/eax: boolean <- call-path-element-match? curr-path, cursor-call-path
1369         compare found?, 0  # false
1370         break-if-= $render-line:cache-cursor-column
1371       }
1372       var dest/edi: (addr int) <- copy cursor-row-addr
1373       {
1374         var src/eax: int <- copy top-row
1375         copy-to *dest, src
1376       }
1377       dest <- copy cursor-col-addr
1378       copy-to *dest, old-col
1379       var cursor-index-in-word/eax: int <- cursor-index curr-word
1380       add-to *dest, cursor-index-in-word
1381     }
1382     # loop update
1383 #?     print-string 0, "next word\n"
1384     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1385     curr-word <- lookup *next-word-ah
1386 #?     {
1387 #?       var foo/eax: int <- copy curr-word
1388 #?       print-int32-hex 0, foo
1389 #?       print-string 0, "\n"
1390 #?     }
1391     increment-final-element curr-path
1392     loop
1393   }
1394   return curr-col
1395 }
1396 
1397 fn callee functions: (addr handle function), word: (addr word), out: (addr handle function) {
1398   var stream-storage: (stream byte 0x10)
1399   var stream/esi: (addr stream byte) <- address stream-storage
1400   emit-word word, stream
1401   find-function functions, stream, out
1402 }
1403 
1404 # Render:
1405 #   - starting at top-row, left-col: final-word
1406 #   - starting somewhere below at left-col: the stack result from interpreting first-world to final-word (inclusive)
1407 #
1408 # Return the farthest column written.
1409 fn render-column screen: (addr screen), functions: (addr handle function), bindings: (addr table), first-line: (addr line), line: (addr line), final-word: (addr word), top-row: int, left-col: int -> _/ecx: int {
1410 #?   print-string 0, "render-column\n"
1411 #?   dump-table bindings
1412   var max-width/esi: int <- copy 0
1413   {
1414     # compute stack
1415     var stack: value-stack
1416     var stack-addr/edi: (addr value-stack) <- address stack
1417     initialize-value-stack stack-addr, 0x10  # max-words
1418     # copy bindings
1419     var bindings2-storage: table
1420     var bindings2/ebx: (addr table) <- address bindings2-storage
1421 #?     print-string 0, "deep copy table {\n"
1422     deep-copy-table bindings, bindings2
1423 #?     print-string 0, "}\n"
1424     evaluate functions, bindings2, first-line, final-word, stack-addr
1425     # indent stack
1426     var indented-col/ebx: int <- copy left-col
1427     indented-col <- add 1  # margin-right
1428     # render stack
1429     var curr-row/edx: int <- copy top-row
1430     curr-row <- add 2  # stack-margin-top
1431     var _max-width/eax: int <- value-stack-max-width stack-addr
1432     max-width <- copy _max-width
1433     {
1434       var top-addr/ecx: (addr int) <- get stack-addr, top
1435       compare *top-addr, 0
1436       break-if-<=
1437       decrement *top-addr
1438       var data-ah/eax: (addr handle array value) <- get stack-addr, data
1439       var data/eax: (addr array value) <- lookup *data-ah
1440       var top/ecx: int <- copy *top-addr
1441       var dest-offset/ecx: (offset value) <- compute-offset data, top
1442       var val/eax: (addr value) <- index data, dest-offset
1443       render-value-at screen, curr-row, indented-col, val, max-width
1444       var height/eax: int <- value-height val
1445       curr-row <- add height
1446       loop
1447     }
1448   }
1449 
1450   max-width <- add 2  # spaces on either side of items on the stack
1451 
1452   # render word, initialize result
1453   reset-formatting screen
1454   move-cursor screen, top-row, left-col
1455   print-word screen, final-word
1456   {
1457     var size/eax: int <- word-length final-word
1458     compare size, max-width
1459     break-if-<=
1460     max-width <- copy size
1461   }
1462 
1463   # post-process right-col
1464   var right-col/ecx: int <- copy left-col
1465   right-col <- add max-width
1466   right-col <- add 1  # margin-right
1467 #?   print-int32-decimal 0, left-col
1468 #?   print-string 0, " => "
1469 #?   print-int32-decimal 0, right-col
1470 #?   print-string 0, "\n"
1471   return right-col
1472 }
1473 
1474 fn clear-canvas _env: (addr environment) {
1475   var env/esi: (addr environment) <- copy _env
1476   var screen-ah/edi: (addr handle screen) <- get env, screen
1477   var _screen/eax: (addr screen) <- lookup *screen-ah
1478   var screen/edi: (addr screen) <- copy _screen
1479   clear-screen screen
1480   var nrows/eax: (addr int) <- get env, nrows
1481   var _sep-col/ecx: (addr int) <- get env, code-separator-col
1482   var sep-col/ecx: int <- copy *_sep-col
1483   draw-vertical-line screen, 1, *nrows, sep-col
1484   # wordstar-style cheatsheet of shortcuts
1485   move-cursor screen, *nrows, 0
1486   start-reverse-video screen
1487   print-string screen, " ctrl-q "
1488   reset-formatting screen
1489   print-string screen, " quit "
1490   var menu-start/ebx: int <- copy sep-col
1491   menu-start <- subtract 0x48  # 72 = half the size of the menu
1492   move-cursor screen, *nrows, menu-start
1493   start-reverse-video screen
1494   print-string screen, " ctrl-a "
1495   reset-formatting screen
1496   print-string screen, " ⏮   "
1497   start-reverse-video screen
1498   print-string screen, " ctrl-b "
1499   reset-formatting screen
1500   print-string screen, " ◀ word  "
1501   start-reverse-video screen
1502   print-string screen, " ctrl-f "
1503   reset-formatting screen
1504   print-string screen, " word ▶  "
1505   start-reverse-video screen
1506   print-string screen, " ctrl-e "
1507   reset-formatting screen
1508   print-string screen, " ⏭         "
1509   start-reverse-video screen
1510   print-string screen, " ctrl-l "
1511   reset-formatting screen
1512   print-string screen, " new line "
1513   start-reverse-video screen
1514   print-string screen, " ctrl-u "
1515   reset-formatting screen
1516   print-string screen, " clear  "
1517   start-reverse-video screen
1518   print-string screen, " ctrl-n "
1519   reset-formatting screen
1520   print-string screen, " name value  "
1521   start-reverse-video screen
1522   print-string screen, " ctrl-d "
1523   reset-formatting screen
1524   print-string screen, " define function  "
1525   # primitives
1526   var dummy/eax: int <- render-primitives screen, *nrows, sep-col
1527   # currently defined functions
1528   render-functions screen, sep-col, env
1529 }
1530 
1531 # return value: top-most row written to
1532 fn render-primitives screen: (addr screen), bottom-margin-row: int, right-col: int -> _/eax: int {
1533   # render primitives from the bottom of the screen upward
1534   var row/ecx: int <- copy bottom-margin-row
1535   row <- subtract 1
1536   var col/edx: int <- copy 1
1537   move-cursor screen, row, col
1538   row, col <- render-primitive-group screen, row, col, right-col, "numbers: ", "+ - * / sqrt  "
1539   row, col <- render-primitive-group screen, row, col, right-col, "arrays: ", "len  "
1540   row, col <- render-primitive-group screen, row, col, right-col, "files: ", "open read slurp lines  "
1541   row, col <- render-primitive-group screen, row, col, right-col, "misc: ", "dup swap  "  # hack: keep these at the right of the bottom row
1542   row, col <- render-primitive-group screen, row, col, right-col, "screens: ", "fake-screen print move up down left right  "
1543   # finally print heading up top
1544   row <- decrement
1545   move-cursor screen, row, 1
1546   start-bold screen
1547   print-string screen, "primitives:"
1548   reset-formatting screen
1549   return row
1550 }
1551 
1552 # start at row, col and print the given strings
1553 # move up one row if there isn't enough room before right-col
1554 # return row, col printed until
1555 fn render-primitive-group screen: (addr screen), _row: int, _col: int, right-col: int, _heading: (addr array byte), _contents: (addr array byte) -> _/ecx: int, _/edx: int {
1556   var row/ecx: int <- copy _row
1557   var col/edx: int <- copy _col
1558   # decrement row if necessary
1559   var new-col/ebx: int <- copy col
1560   var heading/esi: (addr array byte) <- copy _heading
1561   var len1/eax: int <- length heading
1562   new-col <- add len1
1563   var contents/edi: (addr array byte) <- copy _contents
1564   var len2/eax: int <- length contents
1565   new-col <- add len2
1566   var bound/eax: int <- copy right-col
1567   bound <- decrement
1568   {
1569     compare new-col, bound
1570     break-if-<=
1571     row <- decrement
1572     col <- copy 1
1573   }
1574   move-cursor screen, row, col
1575   start-color screen, 0xf6, 7
1576   print-string screen, heading
1577   reset-formatting screen
1578   print-string screen, contents
1579   return row, new-col
1580 }
1581 
1582 fn render-functions screen: (addr screen), right-col: int, _env: (addr environment) {
1583   var row/ecx: int <- copy 1
1584   var dummy-col/edx: int <- copy right-col
1585   var env/esi: (addr environment) <- copy _env
1586   var functions/esi: (addr handle function) <- get env, functions
1587   {
1588     var curr/eax: (addr function) <- lookup *functions
1589     compare curr, 0
1590     break-if-=
1591     row, dummy-col <- render-function-right-aligned screen, row, right-col, curr
1592     functions <- get curr, next
1593     row <- add 1  # inter-function-margin
1594     loop
1595   }
1596 }
1597 
1598 # print function starting at row, right-aligned before right-col
1599 # return row, col printed until
1600 fn render-function-right-aligned screen: (addr screen), row: int, right-col: int, f: (addr function) -> _/ecx: int, _/edx: int {
1601   var col/edx: int <- copy right-col
1602   col <- subtract 1  # function-right-margin
1603   var col2/ebx: int <- copy col
1604   var width/eax: int <- function-width f
1605   col <- subtract width
1606   var new-row/ecx: int <- copy row
1607   var height/eax: int <- function-height f
1608   new-row <- add height
1609   new-row <- decrement
1610   col <- subtract 1  # function-left-padding
1611   start-color screen, 0, 0xf7
1612   clear-rect screen, row, col, new-row, col2
1613   col <- add 1
1614 #?   var dummy/eax: grapheme <- read-key-from-real-keyboard
1615   render-function screen, row, col, f
1616   new-row <- add 1  # function-bottom-margin
1617   col <- subtract 1  # function-left-padding
1618   col <- subtract 1  # function-left-margin
1619   reset-formatting screen
1620   return new-row, col
1621 }
1622 
1623 # render function starting at row, col
1624 # only single-line functions supported for now
1625 fn render-function screen: (addr screen), row: int, col: int, _f: (addr function) {
1626   var f/esi: (addr function) <- copy _f
1627   var args/ecx: (addr handle word) <- get f, args
1628   move-cursor screen, row, col
1629   print-words-in-reverse screen, args
1630   var name-ah/eax: (addr handle array byte) <- get f, name
1631   var name/eax: (addr array byte) <- lookup *name-ah
1632   start-bold screen
1633   print-string screen, name
1634   reset-formatting screen
1635   start-color screen, 0, 0xf7
1636   increment row
1637   add-to col, 2
1638   move-cursor screen, row, col
1639   print-string screen, "≡ "
1640   var body-ah/eax: (addr handle line) <- get f, body
1641   var body/eax: (addr line) <- lookup *body-ah
1642   var body-words-ah/eax: (addr handle word) <- get body, data
1643   print-words screen, body-words-ah
1644 }
1645 
1646 fn real-grapheme? g: grapheme -> _/eax: boolean {
1647   # if g == newline return true
1648   compare g, 0xa
1649   {
1650     break-if-!=
1651     return 1  # true
1652   }
1653   # if g == tab return true
1654   compare g, 9
1655   {
1656     break-if-!=
1657     return 1  # true
1658   }
1659   # if g < 32 return false
1660   compare g, 0x20
1661   {
1662     break-if->=
1663     return 0  # false
1664   }
1665   # if g <= 255 return true
1666   compare g, 0xff
1667   {
1668     break-if->
1669     return 1  # true
1670   }
1671   # if (g&0xff == Esc) it's an escape sequence
1672   and-with g, 0xff
1673   compare g, 0x1b  # Esc
1674   {
1675     break-if-!=
1676     return 0  # false
1677   }
1678   # otherwise return true
1679   return 1  # true
1680 }