https://github.com/akkartik/mu/blob/main/linux/tile/environment.mu
   1 # The architecture that seems to be crystallizing: the environment has two
   2 # areas: functions and sandbox.
   3 #
   4 # Rendering the environment requires rendering all areas.
   5 # Displaying the cursor requires displaying cursor for the area controlling the cursor.
   6 # Processing events for the environment requires processing events for the area controlling the cursor.
   7 #
   8 # Areas can have dialogs.
   9 # There can also be global dialogs (currently just one: goto function).
  10 # Areas are responsible for rendering their dialogs.
  11 # In practice this results in dialogs encapsulating the state they need to
  12 # decide whether to render.
  13 #
  14 # This will be useful if we add more areas in the future.
  15 
  16 type environment {
  17   screen: (handle screen)
  18   functions: (handle function)
  19   sandboxes: (handle sandbox)
  20   partial-function-name: (handle word)
  21   # at most one of these will be set
  22   cursor-function: (handle function)
  23   cursor-sandbox: (handle sandbox)
  24   #
  25   nrows: int
  26   ncols: int
  27   code-separator-col: int
  28 }
  29 
  30 fn initialize-environment _env: (addr environment) {
  31   var env/esi: (addr environment) <- copy _env
  32   # initialize some predefined function definitions
  33   var functions/eax: (addr handle function) <- get env, functions
  34   create-primitive-functions functions
  35   # initialize first sandbox
  36   var sandbox-ah/ecx: (addr handle sandbox) <- get env, sandboxes
  37   allocate sandbox-ah
  38   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
  39   initialize-sandbox sandbox
  40   # initialize cursor sandbox
  41   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
  42   copy-object sandbox-ah, cursor-sandbox-ah
  43   # initialize screen
  44   var screen-ah/eax: (addr handle screen) <- get env, screen
  45   var _screen/eax: (addr screen) <- lookup *screen-ah
  46   var screen/edi: (addr screen) <- copy _screen
  47   var nrows/eax: int <- copy 0
  48   var ncols/ecx: int <- copy 0
  49   nrows, ncols <- screen-size screen
  50   var dest/edx: (addr int) <- get env, nrows
  51   copy-to *dest, nrows
  52   dest <- get env, ncols
  53   copy-to *dest, ncols
  54   var repl-col/ecx: int <- copy ncols
  55   repl-col <- shift-right 1
  56   dest <- get env, code-separator-col
  57   copy-to *dest, repl-col
  58 }
  59 
  60 fn initialize-environment-with-fake-screen _self: (addr environment), nrows: int, ncols: int {
  61   var self/esi: (addr environment) <- copy _self
  62   var screen-ah/eax: (addr handle screen) <- get self, screen
  63   allocate screen-ah
  64   var screen-addr/eax: (addr screen) <- lookup *screen-ah
  65   initialize-screen screen-addr, nrows, ncols
  66   initialize-environment self
  67 }
  68 
  69 #############
  70 # Iterate
  71 #############
  72 
  73 fn process _self: (addr environment), key: grapheme {
  74   var self/esi: (addr environment) <- copy _self
  75   var fn-name-ah/eax: (addr handle word) <- get self, partial-function-name
  76   var fn-name/eax: (addr word) <- lookup *fn-name-ah
  77   compare fn-name, 0
  78   {
  79     break-if-=
  80 #?     print-string 0, "processing goto fn\n"
  81     process-goto-dialog self, key
  82     return
  83   }
  84   var function-ah/eax: (addr handle function) <- get self, cursor-function
  85   var function/eax: (addr function) <- lookup *function-ah
  86   compare function, 0
  87   {
  88     break-if-=
  89 #?     print-string 0, "processing function\n"
  90     process-function self, function, key
  91     return
  92   }
  93   var sandbox-ah/eax: (addr handle sandbox) <- get self, cursor-sandbox
  94   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
  95   compare sandbox, 0
  96   {
  97     break-if-=
  98 #?     print-string 0, "processing sandbox\n"
  99     process-sandbox self, sandbox, key
 100     return
 101   }
 102 }
 103 
 104 # collect new name in partial-function-name, and move the cursor to function with that name
 105 fn process-goto-dialog _self: (addr environment), key: grapheme {
 106   var self/esi: (addr environment) <- copy _self
 107   var fn-name-ah/edi: (addr handle word) <- get self, partial-function-name
 108   # if 'esc' pressed, cancel goto
 109   compare key, 0x1b/esc
 110   $process-goto-dialog:cancel: {
 111     break-if-!=
 112     clear-object fn-name-ah
 113     return
 114   }
 115   # if 'enter' pressed, location function and set cursor to it
 116   compare key, 0xa/enter
 117   $process-goto-dialog:commit: {
 118     break-if-!=
 119 #?     print-string 0, "jump\n"
 120     var fn-name/eax: (addr word) <- lookup *fn-name-ah
 121     var functions/ecx: (addr handle function) <- get self, functions
 122     var dest/edx: (addr handle function) <- get self, cursor-function
 123     callee functions, fn-name, dest
 124     # we won't clear cursor-sandbox until we start supporting multiple sandboxes
 125     clear-object fn-name-ah
 126     # there shouldn't be any need to clear state for other dialogs in the sandbox
 127     return
 128   }
 129   #
 130   compare key, 0x7f/del  # backspace on Macs
 131   $process-goto-dialog:backspace: {
 132     break-if-!=
 133     # if not at start, delete grapheme before cursor
 134     var fn-name/eax: (addr word) <- lookup *fn-name-ah
 135     var at-start?/eax: boolean <- cursor-at-start? fn-name
 136     compare at-start?, 0/false
 137     {
 138       break-if-!=
 139       var fn-name/eax: (addr word) <- lookup *fn-name-ah
 140       delete-before-cursor fn-name
 141     }
 142     return
 143   }
 144   # otherwise insert key within current word
 145   var print?/eax: boolean <- real-grapheme? key
 146   $process-goto-dialog:real-grapheme: {
 147     compare print?, 0/false
 148     break-if-=
 149     var fn-name/eax: (addr word) <- lookup *fn-name-ah
 150     add-grapheme-to-word fn-name, key
 151     return
 152   }
 153   # silently ignore other hotkeys
 154 }
 155 
 156 fn process-function _self: (addr environment), _function: (addr function), key: grapheme {
 157   var self/esi: (addr environment) <- copy _self
 158   var function/edi: (addr function) <- copy _function
 159   process-function-edit self, function, key
 160 }
 161 
 162 fn process-function-edit _self: (addr environment), _function: (addr function), key: grapheme {
 163   var self/esi: (addr environment) <- copy _self
 164   var function/edi: (addr function) <- copy _function
 165   var cursor-word-ah/ebx: (addr handle word) <- get function, cursor-word
 166   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 167   var cursor-word/ecx: (addr word) <- copy _cursor-word
 168   compare key, 0x445b1b/left-arrow
 169   $process-function-edit:key-left-arrow: {
 170     break-if-!=
 171 #?     print-string 0, "left-arrow\n"
 172     # if not at start, move left within current word
 173     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 174     compare at-start?, 0/false
 175     {
 176       break-if-!=
 177 #?       print-string 0, "cursor left within word\n"
 178       cursor-left cursor-word
 179       return
 180     }
 181     # otherwise move to end of previous word
 182     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 183     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 184     {
 185       compare prev-word, 0
 186       break-if-=
 187 #?       print-string 0, "move to previous word\n"
 188       cursor-to-end prev-word
 189       copy-object prev-word-ah, cursor-word-ah
 190     }
 191     return
 192   }
 193   compare key, 0x435b1b/right-arrow
 194   $process-function-edit:key-right-arrow: {
 195     break-if-!=
 196     # if not at end, move right within current word
 197     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 198     compare at-end?, 0/false
 199     {
 200       break-if-!=
 201       cursor-right cursor-word
 202       return
 203     }
 204     # otherwise, move to the next word
 205     var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 206     var next-word/eax: (addr word) <- lookup *next-word-ah
 207     {
 208       compare next-word, 0
 209       break-if-=
 210       cursor-to-start next-word
 211       copy-object next-word-ah, cursor-word-ah
 212     }
 213     return
 214   }
 215   # word-based motions
 216   compare key, 2/ctrl-b
 217   $process-function-edit:prev-word: {
 218     break-if-!=
 219     # jump to previous word if possible
 220     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 221     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 222     {
 223       compare prev-word, 0
 224       break-if-=
 225       cursor-to-end prev-word
 226       copy-object prev-word-ah, cursor-word-ah
 227     }
 228     return
 229   }
 230   compare key, 6/ctrl-f
 231   $process-function-edit:next-word: {
 232     break-if-!=
 233     # jump to previous word if possible
 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       cursor-to-end next-word
 240       copy-object next-word-ah, cursor-word-ah
 241     }
 242     return
 243   }
 244   # line-based motions
 245   compare key, 1/ctrl-a
 246   $process-function-edit:start-of-line: {
 247     break-if-!=
 248     # move cursor to start of first word
 249     var body-ah/eax: (addr handle line) <- get function, body
 250     var body/eax: (addr line) <- lookup *body-ah
 251     var body-contents-ah/ecx: (addr handle word) <- get body, data
 252     copy-object body-contents-ah, cursor-word-ah
 253     var body-contents/eax: (addr word) <- lookup *body-contents-ah
 254     cursor-to-start body-contents
 255     return
 256   }
 257   compare key, 5/ctrl-e
 258   $process-function-edit:end-of-line: {
 259     break-if-!=
 260     # move cursor to end of final word
 261     var body-ah/eax: (addr handle line) <- get function, body
 262     var body/eax: (addr line) <- lookup *body-ah
 263     var body-contents-ah/ecx: (addr handle word) <- get body, data
 264     copy-object body-contents-ah, cursor-word-ah
 265     final-word cursor-word-ah, cursor-word-ah
 266     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 267     cursor-to-end cursor-word
 268     return
 269   }
 270   # bounce to another function
 271   compare key, 7/ctrl-g
 272   $process-function-edit:goto-function: {
 273     break-if-!=
 274     # initialize dialog to name function to jump to
 275     var partial-function-name-ah/eax: (addr handle word) <- get self, partial-function-name
 276     allocate partial-function-name-ah
 277     var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah
 278     initialize-word partial-function-name
 279     return
 280   }
 281   # bounce to sandbox
 282   compare key, 9/tab
 283   $process-function-edit:goto-sandbox: {
 284     break-if-!=
 285     var function-ah/eax: (addr handle function) <- get self, cursor-function
 286     clear-object function-ah
 287     return
 288   }
 289   # editing the current function
 290   compare key, 0x7f/del  # backspace on Macs
 291   $process-function-edit:backspace: {
 292     break-if-!=
 293     # if not at start of some word, delete grapheme before cursor within current word
 294     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 295     compare at-start?, 0/false
 296     {
 297       break-if-!=
 298       delete-before-cursor cursor-word
 299       return
 300     }
 301     # otherwise delete current word and move to end of prev word
 302     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 303     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 304     {
 305       compare prev-word, 0
 306       break-if-=
 307       cursor-to-end prev-word
 308       delete-next prev-word
 309       copy-object prev-word-ah, cursor-word-ah
 310     }
 311     return
 312   }
 313   compare key, 0x20/space
 314   $process-function-edit:space: {
 315     break-if-!=
 316 #?     print-string 0, "space\n"
 317     # if cursor is at start of word, insert word before
 318     {
 319       var at-start?/eax: boolean <- cursor-at-start? cursor-word
 320       compare at-start?, 0/false
 321       break-if-=
 322       var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 323       append-word prev-word-ah
 324       var new-prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 325       copy-object new-prev-word-ah, cursor-word-ah
 326       return
 327     }
 328     # if start of word is quote and grapheme before cursor is not, just insert it as usual
 329     # TODO: support string escaping
 330     {
 331       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 332       compare first-grapheme, 0x22/double-quote
 333       break-if-!=
 334       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 335       compare final-grapheme, 0x22/double-quote
 336       break-if-=
 337       break $process-function-edit:space
 338     }
 339     # if start of word is '[' and grapheme before cursor is not ']', just insert it as usual
 340     # TODO: support nested arrays
 341     {
 342       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 343       compare first-grapheme, 0x5b/[
 344       break-if-!=
 345       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 346       compare final-grapheme, 0x5d/]
 347       break-if-=
 348       break $process-function-edit:space
 349     }
 350     # otherwise insert word after and move cursor to it for the next key
 351     # (but we'll continue to track the current cursor-word for the rest of this function)
 352     append-word cursor-word-ah
 353     var next-word-ah/eax: (addr handle word) <- get cursor-word, next
 354     copy-object next-word-ah, cursor-word-ah
 355     # if cursor is at end of word, that's all
 356     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 357     compare at-end?, 0/false
 358     {
 359       break-if-=
 360       return
 361     }
 362     # otherwise we're in the middle of a word
 363     # move everything after cursor to the (just created) next word
 364     var next-word-ah/eax: (addr handle word) <- get cursor-word, next
 365     var _next-word/eax: (addr word) <- lookup *next-word-ah
 366     var next-word/ebx: (addr word) <- copy _next-word
 367     {
 368       var at-end?/eax: boolean <- cursor-at-end? cursor-word
 369       compare at-end?, 0/false
 370       break-if-!=
 371       var g/eax: grapheme <- pop-after-cursor cursor-word
 372       add-grapheme-to-word next-word, g
 373       loop
 374     }
 375     cursor-to-start next-word
 376     return
 377   }
 378   # otherwise insert key within current word
 379   var g/edx: grapheme <- copy key
 380   var print?/eax: boolean <- real-grapheme? key
 381   $process-function-edit:real-grapheme: {
 382     compare print?, 0/false
 383     break-if-=
 384     add-grapheme-to-word cursor-word, g
 385     return
 386   }
 387   # silently ignore other hotkeys
 388 }
 389 
 390 fn process-sandbox _self: (addr environment), _sandbox: (addr sandbox), key: grapheme {
 391   var self/esi: (addr environment) <- copy _self
 392   var sandbox/edi: (addr sandbox) <- copy _sandbox
 393   var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
 394   var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah?
 395   compare rename-word-mode?, 0
 396   {
 397     break-if-=
 398 #?     print-string 0, "processing sandbox rename\n"
 399     process-sandbox-rename sandbox, key
 400     return
 401   }
 402   var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function
 403   var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah?
 404   compare define-function-mode?, 0
 405   {
 406     break-if-=
 407 #?     print-string 0, "processing function definition\n"
 408     var functions/ecx: (addr handle function) <- get self, functions
 409     process-sandbox-define sandbox, functions, key
 410     return
 411   }
 412 #?   print-string 0, "processing sandbox edit\n"
 413   process-sandbox-edit self, sandbox, key
 414 }
 415 
 416 fn process-sandbox-edit _self: (addr environment), _sandbox: (addr sandbox), key: grapheme {
 417   var self/esi: (addr environment) <- copy _self
 418   var sandbox/edi: (addr sandbox) <- copy _sandbox
 419   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 420   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 421   var cursor-word-ah/ebx: (addr handle word) <- get cursor-call-path, word
 422   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 423   var cursor-word/ecx: (addr word) <- copy _cursor-word
 424   compare key, 0x445b1b/left-arrow
 425   $process-sandbox-edit:key-left-arrow: {
 426     break-if-!=
 427 #?     print-string 0, "left-arrow\n"
 428     # if not at start, move left within current word
 429     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 430     compare at-start?, 0/false
 431     {
 432       break-if-!=
 433 #?       print-string 0, "cursor left within word\n"
 434       cursor-left cursor-word
 435       return
 436     }
 437     # if current word is expanded, move to the rightmost word in its body
 438     {
 439       var cursor-call-path/esi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 440       var expanded-words/edx: (addr handle call-path) <- get sandbox, expanded-words
 441       var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 442       compare curr-word-is-expanded?, 0/false
 443       break-if-=
 444       # update cursor-call-path
 445 #?       print-string 0, "curr word is expanded\n"
 446       var self/ecx: (addr environment) <- copy _self
 447       var functions/ecx: (addr handle function) <- get self, functions
 448       var body: (handle line)
 449       var body-ah/eax: (addr handle line) <- address body
 450       function-body functions, cursor-word-ah, body-ah
 451       var body-addr/eax: (addr line) <- lookup *body-ah
 452       var first-word-ah/edx: (addr handle word) <- get body-addr, data
 453       var final-word-h: (handle word)
 454       var final-word-ah/eax: (addr handle word) <- address final-word-h
 455       final-word first-word-ah, final-word-ah
 456       push-to-call-path-element cursor-call-path, final-word-ah
 457       # move cursor to end of word
 458       var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 459       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 460       var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 461       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 462       cursor-to-end cursor-word
 463       return
 464     }
 465     # if at first word, look for a caller to jump to
 466     $process-sandbox-edit:key-left-arrow-first-word: {
 467       var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 468       var prev-word/eax: (addr word) <- lookup *prev-word-ah
 469       compare prev-word, 0
 470       break-if-!=
 471       $process-sandbox-edit:key-left-arrow-first-word-and-caller: {
 472 #?         print-string 0, "return\n"
 473         {
 474           var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 475           var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 476           var next-cursor-element-ah/edx: (addr handle call-path-element) <- get cursor-call-path, next
 477           var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 478           compare next-cursor-element, 0
 479           break-if-= $process-sandbox-edit:key-left-arrow-first-word-and-caller
 480           copy-object next-cursor-element-ah, cursor-call-path-ah
 481         }
 482         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 483         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 484         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 485         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 486         cursor-word <- copy _cursor-word
 487       }
 488     }
 489     # then move to end of previous word
 490     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 491     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 492     {
 493       compare prev-word, 0
 494       break-if-=
 495 #?       print-string 0, "move to previous word\n"
 496       cursor-to-end prev-word
 497 #?       {
 498 #?         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 499 #?         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 500 #?         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 501 #?         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 502 #?         var cursor-word/ebx: (addr word) <- copy _cursor-word
 503 #?         print-string 0, "word at cursor before: "
 504 #?         print-word 0, cursor-word
 505 #?         print-string 0, "\n"
 506 #?       }
 507       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 508       decrement-final-element cursor-call-path
 509 #?       {
 510 #?         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 511 #?         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 512 #?         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 513 #?         var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 514 #?         var cursor-word/ebx: (addr word) <- copy _cursor-word
 515 #?         print-string 0, "word at cursor after: "
 516 #?         print-word 0, cursor-word
 517 #?         print-string 0, "\n"
 518 #?       }
 519     }
 520     return
 521   }
 522   compare key, 0x435b1b/right-arrow
 523   $process-sandbox-edit:key-right-arrow: {
 524     break-if-!=
 525     # if not at end, move right within current word
 526     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 527     compare at-end?, 0/false
 528     {
 529       break-if-!=
 530 #?       print-string 0, "a\n"
 531       cursor-right cursor-word
 532       return
 533     }
 534     # if at final word, look for a caller to jump to
 535     {
 536       var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 537       var next-word/eax: (addr word) <- lookup *next-word-ah
 538       compare next-word, 0
 539       break-if-!=
 540       var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 541       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 542       var next-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 543       var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 544       compare next-cursor-element, 0
 545       break-if-=
 546       copy-object next-cursor-element-ah, cursor-call-path-ah
 547       return
 548     }
 549     # otherwise, move to the next word
 550     var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 551     var next-word/eax: (addr word) <- lookup *next-word-ah
 552     {
 553       compare next-word, 0
 554       break-if-=
 555 #?       print-string 0, "b\n"
 556       cursor-to-start next-word
 557       # . . cursor-word now out of date
 558       var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
 559       increment-final-element cursor-call-path
 560       # Is the new cursor word expanded? If so, it's a function call. Add a
 561       # new level to the cursor-call-path for the call's body.
 562       $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded: {
 563 #?         print-string 0, "c\n"
 564         {
 565           var expanded-words/eax: (addr handle call-path) <- get sandbox, expanded-words
 566           var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 567           compare curr-word-is-expanded?, 0/false
 568           break-if-= $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded
 569         }
 570         var callee-h: (handle function)
 571         var callee-ah/edx: (addr handle function) <- address callee-h
 572         var functions/ebx: (addr handle function) <- get self, functions
 573         callee functions, next-word, callee-ah
 574         var callee/eax: (addr function) <- lookup *callee-ah
 575         var callee-body-ah/eax: (addr handle line) <- get callee, body
 576         var callee-body/eax: (addr line) <- lookup *callee-body-ah
 577         var callee-body-first-word/edx: (addr handle word) <- get callee-body, data
 578         push-to-call-path-element cursor-call-path, callee-body-first-word
 579         # position cursor at left
 580         var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 581         var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 582         var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 583         var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 584         cursor-to-start cursor-word
 585 #?         print-string 0, "d\n"
 586         return
 587       }
 588     }
 589     return
 590   }
 591   compare key, 0xa/enter
 592   {
 593     break-if-!=
 594     # toggle display of subsidiary stack
 595     toggle-cursor-word sandbox
 596     return
 597   }
 598   compare key, 0xc/ctrl-l
 599   $process-sandbox-edit:new-line: {
 600     break-if-!=
 601     # new line in sandbox
 602     append-line sandbox
 603     return
 604   }
 605   # word-based motions
 606   compare key, 2/ctrl-b
 607   $process-sandbox-edit:prev-word: {
 608     break-if-!=
 609     # jump to previous word at same level
 610     var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev
 611     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 612     {
 613       compare prev-word, 0
 614       break-if-=
 615       cursor-to-end prev-word
 616       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 617       decrement-final-element cursor-call-path
 618       return
 619     }
 620     # if previous word doesn't exist, try to bump up one level
 621     {
 622       var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 623       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 624       var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 625       var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah
 626       compare caller-cursor-element, 0
 627       break-if-=
 628       # check if previous word exists in caller
 629       var caller-word-ah/eax: (addr handle word) <- get caller-cursor-element, word
 630       var caller-word/eax: (addr word) <- lookup *caller-word-ah
 631       var word-before-caller-ah/eax: (addr handle word) <- get caller-word, prev
 632       var word-before-caller/eax: (addr word) <- lookup *word-before-caller-ah
 633       compare word-before-caller, 0
 634       break-if-=
 635       # if so jump to it
 636       drop-from-call-path-element cursor-call-path-ah
 637       decrement-final-element cursor-call-path-ah
 638       return
 639     }
 640   }
 641   compare key, 6/ctrl-f
 642   $process-sandbox-edit:next-word: {
 643     break-if-!=
 644 #?     print-string 0, "AA\n"
 645     # jump to previous word at same level
 646     var next-word-ah/edx: (addr handle word) <- get cursor-word, next
 647     var next-word/eax: (addr word) <- lookup *next-word-ah
 648     {
 649       compare next-word, 0
 650       break-if-=
 651 #?       print-string 0, "BB\n"
 652       cursor-to-end next-word
 653       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 654       increment-final-element cursor-call-path
 655       return
 656     }
 657     # if next word doesn't exist, try to bump up one level
 658 #?     print-string 0, "CC\n"
 659     var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
 660     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 661     var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next
 662     var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah
 663     compare caller-cursor-element, 0
 664     break-if-=
 665 #?     print-string 0, "DD\n"
 666     copy-object caller-cursor-element-ah, cursor-call-path-ah
 667     return
 668   }
 669   compare key, 7/ctrl-g
 670   $process-sandbox-edit:goto-function: {
 671     break-if-!=
 672     # initialize dialog to name function to jump to
 673     var partial-function-name-ah/eax: (addr handle word) <- get self, partial-function-name
 674     allocate partial-function-name-ah
 675     var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah
 676     initialize-word partial-function-name
 677     return
 678   }
 679   # line-based motions
 680   compare key, 1/ctrl-a
 681   $process-sandbox-edit:start-of-line: {
 682     break-if-!=
 683     # move cursor up past all calls and to start of line
 684     var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 685     drop-nested-calls cursor-call-path-ah
 686     move-final-element-to-start-of-line cursor-call-path-ah
 687     # move cursor to start of word
 688     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 689     var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 690     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 691     cursor-to-start cursor-word
 692     # this works as long as the first word isn't expanded
 693     # but we don't expect to see zero-arg functions first-up
 694     return
 695   }
 696   compare key, 5/ctrl-e
 697   $process-sandbox-edit:end-of-line: {
 698     break-if-!=
 699     # move cursor up past all calls and to start of line
 700     var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 701     drop-nested-calls cursor-call-path-ah
 702     move-final-element-to-end-of-line cursor-call-path-ah
 703     # move cursor to end of word
 704     var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 705     var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
 706     var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
 707     cursor-to-end cursor-word
 708     # this works because expanded words lie to the right of their bodies
 709     # so the final word is always guaranteed to be at the top-level
 710     return
 711   }
 712   compare key, 0x15/ctrl-u
 713   $process-sandbox-edit:clear-line: {
 714     break-if-!=
 715     # clear line in sandbox
 716     initialize-sandbox sandbox
 717     return
 718   }
 719   # if cursor is within a call, disable editing hotkeys below
 720   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 721   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 722   var next-cursor-element-ah/eax: (addr handle call-path-element) <- get cursor-call-path, next
 723   var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah
 724   compare next-cursor-element, 0
 725   {
 726     break-if-=
 727     return
 728   }
 729   # - remaining keys only work at the top row outside any function calls
 730   compare key, 0x7f/del  # backspace on Macs
 731   $process-sandbox-edit:backspace: {
 732     break-if-!=
 733     # if not at start of some word, delete grapheme before cursor within current word
 734     var at-start?/eax: boolean <- cursor-at-start? cursor-word
 735     compare at-start?, 0/false
 736     {
 737       break-if-!=
 738       delete-before-cursor cursor-word
 739       return
 740     }
 741     # otherwise delete current word and move to end of prev word
 742     var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 743     var prev-word/eax: (addr word) <- lookup *prev-word-ah
 744     {
 745       compare prev-word, 0
 746       break-if-=
 747       cursor-to-end prev-word
 748       delete-next prev-word
 749       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 750       decrement-final-element cursor-call-path
 751     }
 752     return
 753   }
 754   compare key, 0x20/space
 755   $process-sandbox-edit:space: {
 756     break-if-!=
 757 #?     print-string 0, "space\n"
 758     # if cursor is at start of word, insert word before
 759     {
 760       var at-start?/eax: boolean <- cursor-at-start? cursor-word
 761       compare at-start?, 0/false
 762       break-if-=
 763       var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev
 764       append-word prev-word-ah
 765       var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 766       decrement-final-element cursor-call-path
 767       return
 768     }
 769     # if start of word is quote and grapheme before cursor is not, just insert it as usual
 770     # TODO: support string escaping
 771     {
 772       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 773       compare first-grapheme, 0x22/double-quote
 774       break-if-!=
 775       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 776       compare final-grapheme, 0x22/double-quote
 777       break-if-=
 778       break $process-sandbox-edit:space
 779     }
 780     # if start of word is '[' and grapheme before cursor is not ']', just insert it as usual
 781     # TODO: support nested arrays
 782     {
 783       var first-grapheme/eax: grapheme <- first-grapheme cursor-word
 784       compare first-grapheme, 0x5b/[
 785       break-if-!=
 786       var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word
 787       compare final-grapheme, 0x5d/]
 788       break-if-=
 789       break $process-sandbox-edit:space
 790     }
 791     # otherwise insert word after and move cursor to it for the next key
 792     # (but we'll continue to track the current cursor-word for the rest of this function)
 793     append-word cursor-word-ah
 794     var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 795     increment-final-element cursor-call-path
 796     # if cursor is at end of word, that's all
 797     var at-end?/eax: boolean <- cursor-at-end? cursor-word
 798     compare at-end?, 0/false
 799     {
 800       break-if-=
 801       return
 802     }
 803     # otherwise we're in the middle of a word
 804     # move everything after cursor to the (just created) next word
 805     var next-word-ah/eax: (addr handle word) <- get cursor-word, next
 806     var _next-word/eax: (addr word) <- lookup *next-word-ah
 807     var next-word/ebx: (addr word) <- copy _next-word
 808     {
 809       var at-end?/eax: boolean <- cursor-at-end? cursor-word
 810       compare at-end?, 0/false
 811       break-if-!=
 812       var g/eax: grapheme <- pop-after-cursor cursor-word
 813       add-grapheme-to-word next-word, g
 814       loop
 815     }
 816     cursor-to-start next-word
 817     return
 818   }
 819   compare key, 0xe/ctrl-n
 820   $process:rename-word: {
 821     break-if-!=
 822     # TODO: ensure current word is not a function
 823     # rename word at cursor
 824     var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-cursor-word
 825     allocate new-name-ah
 826     var new-name/eax: (addr word) <- lookup *new-name-ah
 827     initialize-word new-name
 828     return
 829   }
 830   compare key, 4/ctrl-d
 831   $process:define-function: {
 832     break-if-!=
 833     # define function out of line at cursor
 834     var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-function
 835     allocate new-name-ah
 836     var new-name/eax: (addr word) <- lookup *new-name-ah
 837     initialize-word new-name
 838     return
 839   }
 840   # otherwise insert key within current word
 841   var g/edx: grapheme <- copy key
 842   var print?/eax: boolean <- real-grapheme? key
 843   $process-sandbox-edit:real-grapheme: {
 844     compare print?, 0/false
 845     break-if-=
 846     add-grapheme-to-word cursor-word, g
 847     return
 848   }
 849   # silently ignore other hotkeys
 850 }
 851 
 852 # collect new name in partial-name-for-cursor-word, and then rename the word
 853 # at cursor to it
 854 # Precondition: cursor-call-path is a singleton (not within a call)
 855 fn process-sandbox-rename _sandbox: (addr sandbox), key: grapheme {
 856   var sandbox/esi: (addr sandbox) <- copy _sandbox
 857   var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-cursor-word
 858   # if 'esc' pressed, cancel rename
 859   compare key, 0x1b/esc
 860   $process-sandbox-rename:cancel: {
 861     break-if-!=
 862     clear-object new-name-ah
 863     return
 864   }
 865   # if 'enter' pressed, perform rename
 866   compare key, 0xa/enter
 867   $process-sandbox-rename:commit: {
 868     break-if-!=
 869 #?     print-string 0, "rename\n"
 870     # new line
 871     var new-line-h: (handle line)
 872     var new-line-ah/eax: (addr handle line) <- address new-line-h
 873     allocate new-line-ah
 874     var new-line/eax: (addr line) <- lookup *new-line-ah
 875     initialize-line new-line
 876     var new-line-word-ah/ecx: (addr handle word) <- get new-line, data
 877     {
 878       # move word at cursor to new line
 879       var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 880       var cursor/eax: (addr call-path-element) <- lookup *cursor-ah
 881       var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word
 882 #?       print-string 0, "cursor before at word "
 883 #?       {
 884 #?         var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 885 #?         print-word 0, cursor-word
 886 #?         print-string 0, "\n"
 887 #?       }
 888       move-word-contents word-at-cursor-ah, new-line-word-ah
 889       # copy name to word at cursor
 890       copy-word-contents-before-cursor new-name-ah, word-at-cursor-ah
 891 #?       print-string 0, "cursor after at word "
 892 #?       {
 893 #?         var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 894 #?         print-word 0, cursor-word
 895 #?         print-string 0, "\n"
 896 #?         var foo/eax: int <- copy cursor-word
 897 #?         print-int32-hex 0, foo
 898 #?         print-string 0, "\n"
 899 #?       }
 900 #?       print-string 0, "new name word "
 901 #?       {
 902 #?         var new-name/eax: (addr word) <- lookup *new-name-ah
 903 #?         print-word 0, new-name
 904 #?         print-string 0, "\n"
 905 #?         var foo/eax: int <- copy new-name
 906 #?         print-int32-hex 0, foo
 907 #?         print-string 0, "\n"
 908 #?       }
 909     }
 910     # prepend '=' to name
 911     {
 912       var new-name/eax: (addr word) <- lookup *new-name-ah
 913       cursor-to-start new-name
 914       add-grapheme-to-word new-name, 0x3d/=
 915     }
 916     # append name to new line
 917     chain-words new-line-word-ah, new-name-ah
 918     # new-line->next = sandbox->data
 919     var new-line-next/ecx: (addr handle line) <- get new-line, next
 920     var sandbox-slot/edx: (addr handle line) <- get sandbox, data
 921     copy-object sandbox-slot, new-line-next
 922     # sandbox->data = new-line
 923     copy-handle new-line-h, sandbox-slot
 924     # clear partial-name-for-cursor-word
 925     clear-object new-name-ah
 926 #?     var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 927 #?     var cursor/eax: (addr call-path-element) <- lookup *cursor-ah
 928 #?     var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word
 929 #?     print-string 0, "cursor after rename: "
 930 #?     {
 931 #?       var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah
 932 #?       print-word 0, cursor-word
 933 #?       print-string 0, " -- "
 934 #?       var foo/eax: int <- copy cursor-word
 935 #?       print-int32-hex 0, foo
 936 #?       print-string 0, "\n"
 937 #?     }
 938     return
 939   }
 940   #
 941   compare key, 0x7f/del  # backspace on Macs
 942   $process-sandbox-rename:backspace: {
 943     break-if-!=
 944     # if not at start, delete grapheme before cursor
 945     var new-name/eax: (addr word) <- lookup *new-name-ah
 946     var at-start?/eax: boolean <- cursor-at-start? new-name
 947     compare at-start?, 0/false
 948     {
 949       break-if-!=
 950       var new-name/eax: (addr word) <- lookup *new-name-ah
 951       delete-before-cursor new-name
 952     }
 953     return
 954   }
 955   # otherwise insert key within current word
 956   var print?/eax: boolean <- real-grapheme? key
 957   $process-sandbox-rename:real-grapheme: {
 958     compare print?, 0/false
 959     break-if-=
 960     var new-name/eax: (addr word) <- lookup *new-name-ah
 961     add-grapheme-to-word new-name, key
 962     return
 963   }
 964   # silently ignore other hotkeys
 965 }
 966 
 967 # collect new name in partial-name-for-function, and then define the last line
 968 # of the sandbox to be a new function with that name. Replace the last line
 969 # with a call to the appropriate function.
 970 # Precondition: cursor-call-path is a singleton (not within a call)
 971 fn process-sandbox-define _sandbox: (addr sandbox), functions: (addr handle function), key: grapheme {
 972   var sandbox/esi: (addr sandbox) <- copy _sandbox
 973   var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-function
 974   # if 'esc' pressed, cancel define
 975   compare key, 0x1b/esc
 976   $process-sandbox-define:cancel: {
 977     break-if-!=
 978     clear-object new-name-ah
 979     return
 980   }
 981   # if 'enter' pressed, perform define
 982   compare key, 0xa/enter
 983   $process-sandbox-define:commit: {
 984     break-if-!=
 985 #?     print-string 0, "define\n"
 986     # create new function
 987     var new-function: (handle function)
 988     var new-function-ah/ecx: (addr handle function) <- address new-function
 989     allocate new-function-ah
 990     var _new-function/eax: (addr function) <- lookup *new-function-ah
 991     var new-function/ebx: (addr function) <- copy _new-function
 992     var dest/edx: (addr handle function) <- get new-function, next
 993     copy-object functions, dest
 994     copy-object new-function-ah, functions
 995     # set function name to new-name
 996     var new-name/eax: (addr word) <- lookup *new-name-ah
 997     var dest/edx: (addr handle array byte) <- get new-function, name
 998     word-to-string new-name, dest
 999     # move final line to body
1000     var body-ah/eax: (addr handle line) <- get new-function, body
1001     allocate body-ah
1002     var body/eax: (addr line) <- lookup *body-ah
1003     var body-contents/ecx: (addr handle word) <- get body, data
1004     var final-line-storage: (handle line)
1005     var final-line-ah/eax: (addr handle line) <- address final-line-storage
1006     final-line sandbox, final-line-ah
1007     var final-line/eax: (addr line) <- lookup *final-line-ah
1008     var final-line-contents/eax: (addr handle word) <- get final-line, data
1009     copy-object final-line-contents, body-contents
1010     var cursor-word-ah/ecx: (addr handle word) <- get new-function, cursor-word
1011     copy-object final-line-contents, cursor-word-ah
1012     {
1013       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1014       cursor-to-start cursor-word
1015     }
1016     #
1017     copy-unbound-words-to-args functions
1018     #
1019     var empty-word: (handle word)
1020     copy-handle empty-word, final-line-contents
1021     construct-call functions, final-line-contents
1022     # clear partial-name-for-function
1023     var empty-word: (handle word)
1024     copy-handle empty-word, new-name-ah
1025     # update cursor
1026     var final-line/eax: (addr line) <- lookup final-line-storage
1027     var cursor-call-path-ah/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
1028     allocate cursor-call-path-ah  # leak
1029     initialize-path-from-line final-line, cursor-call-path-ah
1030     return
1031   }
1032   #
1033   compare key, 0x7f/del  # backspace on Macs
1034   $process-sandbox-define:backspace: {
1035     break-if-!=
1036     # if not at start, delete grapheme before cursor
1037     var new-name/eax: (addr word) <- lookup *new-name-ah
1038     var at-start?/eax: boolean <- cursor-at-start? new-name
1039     compare at-start?, 0/false
1040     {
1041       break-if-!=
1042       var new-name/eax: (addr word) <- lookup *new-name-ah
1043       delete-before-cursor new-name
1044     }
1045     return
1046   }
1047   # otherwise insert key within current word
1048   var print?/eax: boolean <- real-grapheme? key
1049   $process-sandbox-define:real-grapheme: {
1050     compare print?, 0/false
1051     break-if-=
1052     var new-name/eax: (addr word) <- lookup *new-name-ah
1053     add-grapheme-to-word new-name, key
1054     return
1055   }
1056   # silently ignore other hotkeys
1057 }
1058 
1059 # extract from the body of the first function in 'functions' all words that
1060 # aren't defined in the rest of 'functions'. Prepend them in reverse order.
1061 # Assumes function body is a single line for now.
1062 fn copy-unbound-words-to-args _functions: (addr handle function) {
1063   # target
1064   var target-ah/eax: (addr handle function) <- copy _functions
1065   var _target/eax: (addr function) <- lookup *target-ah
1066   var target/esi: (addr function) <- copy _target
1067   var dest-ah/edi: (addr handle word) <- get target, args
1068   # next
1069   var functions-ah/edx: (addr handle function) <- get target, next
1070   # src
1071   var line-ah/eax: (addr handle line) <- get target, body
1072   var line/eax: (addr line) <- lookup *line-ah
1073   var curr-ah/eax: (addr handle word) <- get line, data
1074   var curr/eax: (addr word) <- lookup *curr-ah
1075   {
1076     compare curr, 0
1077     break-if-=
1078     $copy-unbound-words-to-args:loop-iter: {
1079       # is it a number?
1080       {
1081         var int?/eax: boolean <- word-is-decimal-integer? curr
1082         compare int?, 0/false
1083         break-if-!= $copy-unbound-words-to-args:loop-iter
1084       }
1085       # is it a pre-existing function?
1086       var bound?/ebx: boolean <- bound-function? curr, functions-ah
1087       compare bound?, 0/false
1088       break-if-!=
1089       # is it already bound as an arg?
1090       var dup?/ebx: boolean <- arg-exists? _functions, curr  # _functions = target-ah
1091       compare dup?, 0/false
1092       break-if-!= $copy-unbound-words-to-args:loop-iter
1093       # push copy of curr before dest-ah
1094       var rest-h: (handle word)
1095       var rest-ah/ecx: (addr handle word) <- address rest-h
1096       copy-object dest-ah, rest-ah
1097       copy-word curr, dest-ah
1098       chain-words dest-ah, rest-ah
1099     }
1100     var next-ah/ecx: (addr handle word) <- get curr, next
1101     curr <- lookup *next-ah
1102     loop
1103   }
1104 }
1105 
1106 fn bound-function? w: (addr word), functions-ah: (addr handle function) -> _/ebx: boolean {
1107   var result/ebx: boolean <- copy 1/true
1108   {
1109     ## numbers
1110     # if w == "+" return true
1111     var subresult/eax: boolean <- word-equal? w, "+"
1112     compare subresult, 0/false
1113     break-if-!=
1114     # if w == "-" return true
1115     subresult <- word-equal? w, "-"
1116     compare subresult, 0/false
1117     break-if-!=
1118     # if w == "*" return true
1119     subresult <- word-equal? w, "*"
1120     compare subresult, 0/false
1121     break-if-!=
1122     # if w == "/" return true
1123     subresult <- word-equal? w, "/"
1124     compare subresult, 0/false
1125     break-if-!=
1126     # if w == "sqrt" return true
1127     subresult <- word-equal? w, "sqrt"
1128     compare subresult, 0/false
1129     break-if-!=
1130     ## strings/arrays
1131     # if w == "len" return true
1132     subresult <- word-equal? w, "len"
1133     compare subresult, 0/false
1134     break-if-!=
1135     ## files
1136     # if w == "open" return true
1137     subresult <- word-equal? w, "open"
1138     compare subresult, 0/false
1139     break-if-!=
1140     # if w == "read" return true
1141     subresult <- word-equal? w, "read"
1142     compare subresult, 0/false
1143     break-if-!=
1144     # if w == "slurp" return true
1145     subresult <- word-equal? w, "slurp"
1146     compare subresult, 0/false
1147     break-if-!=
1148     # if w == "lines" return true
1149     subresult <- word-equal? w, "lines"
1150     compare subresult, 0/false
1151     break-if-!=
1152     ## screens
1153     # if w == "fake-screen" return true
1154     subresult <- word-equal? w, "fake-screen"
1155     compare subresult, 0/false
1156     break-if-!=
1157     # if w == "print" return true
1158     subresult <- word-equal? w, "print"
1159     compare subresult, 0/false
1160     break-if-!=
1161     # if w == "move" return true
1162     subresult <- word-equal? w, "move"
1163     compare subresult, 0/false
1164     break-if-!=
1165     # if w == "up" return true
1166     subresult <- word-equal? w, "up"
1167     compare subresult, 0/false
1168     break-if-!=
1169     # if w == "down" return true
1170     subresult <- word-equal? w, "down"
1171     compare subresult, 0/false
1172     break-if-!=
1173     # if w == "left" return true
1174     subresult <- word-equal? w, "left"
1175     compare subresult, 0/false
1176     break-if-!=
1177     # if w == "right" return true
1178     subresult <- word-equal? w, "right"
1179     compare subresult, 0/false
1180     break-if-!=
1181     ## hacks
1182     # if w == "dup" return true
1183     subresult <- word-equal? w, "dup"
1184     compare subresult, 0/false
1185     break-if-!=
1186     # if w == "swap" return true
1187     subresult <- word-equal? w, "swap"
1188     compare subresult, 0/false
1189     break-if-!=
1190     # return w in functions
1191     var out-h: (handle function)
1192     var out/eax: (addr handle function) <- address out-h
1193     callee functions-ah, w, out
1194     var found?/eax: (addr function) <- lookup *out
1195     result <- copy found?
1196   }
1197   return result
1198 }
1199 
1200 fn arg-exists? _f-ah: (addr handle function), arg: (addr word) -> _/ebx: boolean {
1201   var f-ah/eax: (addr handle function) <- copy _f-ah
1202   var f/eax: (addr function) <- lookup *f-ah
1203   var args-ah/eax: (addr handle word) <- get f, args
1204   var result/ebx: boolean <- word-exists? args-ah, arg
1205   return result
1206 }
1207 
1208 # construct a call to `f` with copies of exactly its args
1209 fn construct-call _f-ah: (addr handle function), _dest-ah: (addr handle word) {
1210   var f-ah/eax: (addr handle function) <- copy _f-ah
1211   var _f/eax: (addr function) <- lookup *f-ah
1212   var f/esi: (addr function) <- copy _f
1213   # append args in reverse
1214   var args-ah/eax: (addr handle word) <- get f, args
1215   var dest-ah/edi: (addr handle word) <- copy _dest-ah
1216   copy-words-in-reverse args-ah, dest-ah
1217   # append name
1218   var name-ah/eax: (addr handle array byte) <- get f, name
1219   var name/eax: (addr array byte) <- lookup *name-ah
1220   append-word-at-end-with dest-ah, name
1221 }
1222 
1223 fn word-index _words: (addr handle word), _n: int, out: (addr handle word) {
1224   var n/ecx: int <- copy _n
1225   {
1226     compare n, 0
1227     break-if-!=
1228     copy-object _words, out
1229     return
1230   }
1231   var words-ah/eax: (addr handle word) <- copy _words
1232   var words/eax: (addr word) <- lookup *words-ah
1233   var next/eax: (addr handle word) <- get words, next
1234   n <- decrement
1235   word-index next, n, out
1236 }
1237 
1238 fn toggle-cursor-word _sandbox: (addr sandbox) {
1239   var sandbox/esi: (addr sandbox) <- copy _sandbox
1240   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
1241   var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
1242 #?   print-string 0, "cursor call path: "
1243 #?   dump-call-path-element 0, cursor-call-path
1244 #?   print-string 0, "expanded words:\n"
1245 #?   dump-call-paths 0, expanded-words
1246   var already-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
1247   compare already-expanded?, 0/false
1248   {
1249     break-if-!=
1250 #?     print-string 0, "expand\n"
1251     # if not already-expanded, insert
1252     insert-in-call-path expanded-words cursor-call-path
1253 #?     print-string 0, "expanded words now:\n"
1254 #?     dump-call-paths 0, expanded-words
1255     return
1256   }
1257   {
1258     break-if-=
1259     # otherwise delete
1260     delete-in-call-path expanded-words cursor-call-path
1261   }
1262 }
1263 
1264 fn append-line _sandbox: (addr sandbox) {
1265   var sandbox/esi: (addr sandbox) <- copy _sandbox
1266   var line-ah/ecx: (addr handle line) <- get sandbox, data
1267   {
1268     var line/eax: (addr line) <- lookup *line-ah
1269     var next-line-ah/edx: (addr handle line) <- get line, next
1270     var next-line/eax: (addr line) <- lookup *next-line-ah
1271     compare next-line, 0
1272     break-if-=
1273     line-ah <- copy next-line-ah
1274     loop
1275   }
1276   var line/eax: (addr line) <- lookup *line-ah
1277   var final-line-ah/edx: (addr handle line) <- get line, next
1278   allocate final-line-ah
1279   var final-line/eax: (addr line) <- lookup *final-line-ah
1280   initialize-line final-line
1281   var final-prev/eax: (addr handle line) <- get final-line, prev
1282   copy-object line-ah, final-prev
1283   # clear cursor
1284   var final-line/eax: (addr line) <- lookup *final-line-ah
1285   var word-ah/ecx: (addr handle word) <- get final-line, data
1286   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1287   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1288   var dest/eax: (addr handle word) <- get cursor-call-path, word
1289   copy-object word-ah, dest
1290 }
1291 
1292 #############
1293 # Visualize
1294 #############
1295 
1296 fn evaluate-environment _env: (addr environment), stack: (addr value-stack) {
1297   var env/esi: (addr environment) <- copy _env
1298   # functions
1299   var functions/edx: (addr handle function) <- get env, functions
1300   # line
1301   var sandbox-ah/esi: (addr handle sandbox) <- get env, sandboxes
1302   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
1303   var line-ah/eax: (addr handle line) <- get sandbox, data
1304   var _line/eax: (addr line) <- lookup *line-ah
1305   var line/esi: (addr line) <- copy _line
1306   evaluate functions, 0, line, 0, stack
1307 }
1308 
1309 fn render _env: (addr environment) {
1310 #?   print-string 0, "== render\n"
1311   var env/esi: (addr environment) <- copy _env
1312   clear-canvas env
1313   # menu
1314   render-menu env
1315   # screen
1316   var screen-ah/eax: (addr handle screen) <- get env, screen
1317   var _screen/eax: (addr screen) <- lookup *screen-ah
1318   var screen/edi: (addr screen) <- copy _screen
1319   # functions
1320   var sep-col/eax: (addr int) <- get env, code-separator-col
1321   var functions/edx: (addr handle function) <- get env, functions
1322   render-functions screen, *sep-col, env
1323   # sandbox
1324   var repl-col/ecx: int <- copy *sep-col
1325   repl-col <- add 2/repl-margin-left
1326   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
1327   var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah
1328   # bindings
1329   var bindings-storage: table
1330   var bindings/ebx: (addr table) <- address bindings-storage
1331   initialize-table bindings, 0x10
1332 #?   print-string 0, "render-sandbox {\n"
1333   render-sandbox screen, functions, bindings, cursor-sandbox, 3, repl-col
1334 #?   print-string 0, "render-sandbox }\n"
1335   # dialogs
1336   render-goto-dialog screen, env
1337   #
1338   position-cursor screen, env
1339 }
1340 
1341 # draw a wordstar-style cheatsheet of shortcuts on the bottom line of the screen
1342 fn render-menu _env: (addr environment) {
1343   var env/esi: (addr environment) <- copy _env
1344   var cursor-function-ah/eax: (addr handle function) <- get env, cursor-function
1345   var cursor-function/eax: (addr function) <- lookup *cursor-function-ah
1346   {
1347     compare cursor-function, 0
1348     break-if-=
1349     render-function-menu env
1350     return
1351   }
1352   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
1353   var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah
1354   {
1355     compare cursor-sandbox, 0
1356     break-if-=
1357     render-sandbox-menu env
1358     return
1359   }
1360 }
1361 
1362 # HACK: areas currently responsible for positioning their dialogs' cursors. So
1363 # we just do nothing here if a dialog is up.
1364 fn position-cursor screen: (addr screen), _env: (addr environment) {
1365   var env/esi: (addr environment) <- copy _env
1366   var goto-function-ah/eax: (addr handle word) <- get env, partial-function-name
1367   var goto-function/eax: (addr word) <- lookup *goto-function-ah
1368   {
1369     compare goto-function, 0
1370     break-if-=
1371     return
1372   }
1373   var cursor-function-ah/eax: (addr handle function) <- get env, cursor-function
1374   var cursor-function/eax: (addr function) <- lookup *cursor-function-ah
1375   {
1376     compare cursor-function, 0
1377     break-if-=
1378     var cursor-row/ecx: (addr int) <- get cursor-function, cursor-row
1379     var cursor-col/eax: (addr int) <- get cursor-function, cursor-col
1380     move-cursor screen, *cursor-row, *cursor-col
1381     return
1382   }
1383   var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox
1384   var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah
1385   {
1386     compare cursor-sandbox, 0
1387     break-if-=
1388     # if in a dialog, return
1389     {
1390       var partial-word-rename-ah/eax: (addr handle word) <- get cursor-sandbox, partial-name-for-cursor-word
1391       var partial-word-rename/eax: (addr word) <- lookup *partial-word-rename-ah
1392       compare partial-word-rename, 0
1393       break-if-=
1394       return
1395     }
1396     {
1397       var partial-function-name-ah/eax: (addr handle word) <- get cursor-sandbox, partial-name-for-function
1398       var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah
1399       compare partial-function-name, 0
1400       break-if-=
1401       return
1402     }
1403     var cursor-row/ecx: (addr int) <- get cursor-sandbox, cursor-row
1404     var cursor-col/eax: (addr int) <- get cursor-sandbox, cursor-col
1405     move-cursor screen, *cursor-row, *cursor-col
1406   }
1407 }
1408 
1409 fn render-goto-dialog screen: (addr screen), _env: (addr environment) {
1410   var env/esi: (addr environment) <- copy _env
1411   var goto-function-mode-ah?/eax: (addr handle word) <- get env, partial-function-name
1412   var goto-function-mode?/eax: (addr word) <- lookup *goto-function-mode-ah?
1413   compare goto-function-mode?, 0/false
1414   break-if-=
1415   # clear a space for the dialog
1416   var top-row/ebx: int <- copy 3
1417   var bottom-row/edx: int <- copy 9
1418   var sep-col/eax: (addr int) <- get env, code-separator-col
1419   var left-col/ecx: int <- copy *sep-col
1420   left-col <- subtract 0x10
1421   var right-col/eax: int <- copy *sep-col
1422   right-col <- add 0x10
1423   clear-rect screen, top-row, left-col, bottom-row, right-col
1424   draw-box screen, top-row, left-col, bottom-row, right-col
1425   # render a little menu for the dialog
1426   var menu-row/eax: int <- copy bottom-row
1427   menu-row <- decrement
1428   var menu-col/edx: int <- copy left-col
1429   menu-col <- add 2
1430   move-cursor screen, menu-row, menu-col
1431   start-reverse-video screen
1432   print-string screen, " esc "
1433   reset-formatting screen
1434   print-string screen, " cancel  "
1435   start-reverse-video screen
1436   print-string screen, " enter "
1437   reset-formatting screen
1438   print-string screen, " jump  "
1439   # draw the word, positioned appropriately around the cursor
1440   var start-col/ecx: int <- copy left-col
1441   start-col <- increment
1442   move-cursor screen, 6, start-col  # cursor-row
1443   var word-ah?/edx: (addr handle word) <- get env, partial-function-name
1444   var word/eax: (addr word) <- lookup *word-ah?
1445   print-word screen, word
1446 }
1447 
1448 fn render-sandbox screen: (addr screen), functions: (addr handle function), bindings: (addr table), _sandbox: (addr sandbox), top-row: int, left-col: int {
1449   var sandbox/esi: (addr sandbox) <- copy _sandbox
1450   # line
1451   var curr-line-ah/eax: (addr handle line) <- get sandbox, data
1452   var _curr-line/eax: (addr line) <- lookup *curr-line-ah
1453   var curr-line/ecx: (addr line) <- copy _curr-line
1454   #
1455   var curr-row/edx: int <- copy top-row
1456   # cursor row, col
1457   var cursor-row-addr: (addr int)
1458   var tmp/eax: (addr int) <- get sandbox, cursor-row
1459   copy-to cursor-row-addr, tmp
1460   var cursor-col-addr: (addr int)
1461   tmp <- get sandbox, cursor-col
1462   copy-to cursor-col-addr, tmp
1463   # render all but final line without stack
1464 #?   print-string 0, "render all but final line\n"
1465   {
1466     var next-line-ah/eax: (addr handle line) <- get curr-line, next
1467     var next-line/eax: (addr line) <- lookup *next-line-ah
1468     compare next-line, 0
1469     break-if-=
1470     {
1471       var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1472       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1473       var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1474       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1475       # 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
1476       render-line-without-stack screen, curr-line, curr-row, left-col, cursor-word, cursor-row-addr, cursor-col-addr
1477     }
1478     curr-line <- copy next-line
1479     curr-row <- add 2
1480     loop
1481   }
1482   #
1483   render-final-line-with-stack screen, functions, bindings, sandbox, curr-row, left-col, cursor-row-addr, cursor-col-addr
1484   # at most one of the following dialogs will be rendered
1485   render-rename-dialog screen, sandbox
1486   render-define-dialog screen, sandbox
1487 }
1488 
1489 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) {
1490   var sandbox/esi: (addr sandbox) <- copy _sandbox
1491   # expanded-words
1492   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
1493   # cursor-word
1494   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1495   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1496   var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1497   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1498   var cursor-word/ebx: (addr word) <- copy _cursor-word
1499 #?   print-string 0, "word at cursor: "
1500 #?   print-word 0, cursor-word
1501 #?   print-string 0, "\n"
1502   # cursor-call-path
1503   var cursor-call-path: (addr handle call-path-element)
1504   {
1505     var src/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1506     copy-to cursor-call-path, src
1507   }
1508   # first line
1509   var first-line-ah/eax: (addr handle line) <- get sandbox, data
1510   var _first-line/eax: (addr line) <- lookup *first-line-ah
1511   var first-line/edx: (addr line) <- copy _first-line
1512   # final line
1513   var final-line-storage: (handle line)
1514   var final-line-ah/eax: (addr handle line) <- address final-line-storage
1515   final-line sandbox, final-line-ah
1516   var final-line/eax: (addr line) <- lookup *final-line-ah
1517   # curr-path
1518   var curr-path-storage: (handle call-path-element)
1519   var curr-path/ecx: (addr handle call-path-element) <- address curr-path-storage
1520   allocate curr-path  # leak
1521   initialize-path-from-line final-line, curr-path
1522   #
1523   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
1524 }
1525 
1526 fn final-line _sandbox: (addr sandbox), out: (addr handle line) {
1527   var sandbox/esi: (addr sandbox) <- copy _sandbox
1528   var curr-line-ah/ecx: (addr handle line) <- get sandbox, data
1529   {
1530     var curr-line/eax: (addr line) <- lookup *curr-line-ah
1531     var next-line-ah/edx: (addr handle line) <- get curr-line, next
1532     var next-line/eax: (addr line) <- lookup *next-line-ah
1533     compare next-line, 0
1534     break-if-=
1535     curr-line-ah <- copy next-line-ah
1536     loop
1537   }
1538   copy-object curr-line-ah, out
1539 }
1540 
1541 fn render-rename-dialog screen: (addr screen), _sandbox: (addr sandbox) {
1542   var sandbox/edi: (addr sandbox) <- copy _sandbox
1543   var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1544   var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah?
1545   compare rename-word-mode?, 0/false
1546   break-if-=
1547   # clear a space for the dialog
1548   var cursor-row/ebx: (addr int) <- get sandbox, cursor-row
1549   var top-row/eax: int <- copy *cursor-row
1550   top-row <- subtract 3
1551   var bottom-row/ecx: int <- copy *cursor-row
1552   bottom-row <- add 3
1553   var cursor-col/ebx: (addr int) <- get sandbox, cursor-col
1554   var left-col/edx: int <- copy *cursor-col
1555   left-col <- subtract 0x10
1556   var right-col/ebx: int <- copy *cursor-col
1557   right-col <- add 0x10
1558   clear-rect screen, top-row, left-col, bottom-row, right-col
1559   draw-box screen, top-row, left-col, bottom-row, right-col
1560   # render a little menu for the dialog
1561   var menu-row/ecx: int <- copy bottom-row
1562   menu-row <- decrement
1563   var menu-col/edx: int <- copy left-col
1564   menu-col <- add 2
1565   move-cursor screen, menu-row, menu-col
1566   start-reverse-video screen
1567   print-string screen, " esc "
1568   reset-formatting screen
1569   print-string screen, " cancel  "
1570   start-reverse-video screen
1571   print-string screen, " enter "
1572   reset-formatting screen
1573   print-string screen, " rename  "
1574   # draw the word, positioned appropriately around the cursor
1575   var cursor-col/ebx: (addr int) <- get sandbox, cursor-col
1576   var start-col/ecx: int <- copy *cursor-col
1577   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1578   var word/eax: (addr word) <- lookup *word-ah?
1579   var cursor-index/eax: int <- cursor-index word
1580   start-col <- subtract cursor-index
1581   var cursor-row/ebx: (addr int) <- get sandbox, cursor-row
1582   move-cursor screen, *cursor-row, start-col
1583   var word/eax: (addr word) <- lookup *word-ah?
1584   print-word screen, word
1585 }
1586 
1587 fn render-define-dialog screen: (addr screen), _sandbox: (addr sandbox) {
1588   var sandbox/edi: (addr sandbox) <- copy _sandbox
1589   var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function
1590   var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah?
1591   compare define-function-mode?, 0/false
1592   break-if-=
1593   # clear a space for the dialog
1594   var cursor-row/ebx: (addr int) <- get sandbox, cursor-row
1595   var top-row/eax: int <- copy *cursor-row
1596   top-row <- subtract 3
1597   var bottom-row/ecx: int <- copy *cursor-row
1598   bottom-row <- add 3
1599   var cursor-col/ebx: (addr int) <- get sandbox, cursor-col
1600   var left-col/edx: int <- copy *cursor-col
1601   left-col <- subtract 0x10
1602   var right-col/ebx: int <- copy *cursor-col
1603   right-col <- add 0x10
1604   clear-rect screen, top-row, left-col, bottom-row, right-col
1605   draw-box screen, top-row, left-col, bottom-row, right-col
1606   # render a little menu for the dialog
1607   var menu-row/ecx: int <- copy bottom-row
1608   menu-row <- decrement
1609   var menu-col/edx: int <- copy left-col
1610   menu-col <- add 2
1611   move-cursor screen, menu-row, menu-col
1612   start-reverse-video screen
1613   print-string screen, " esc "
1614   reset-formatting screen
1615   print-string screen, " cancel  "
1616   start-reverse-video screen
1617   print-string screen, " enter "
1618   reset-formatting screen
1619   print-string screen, " define  "
1620   # draw the word, positioned appropriately around the cursor
1621   var cursor-col/ebx: (addr int) <- get sandbox, cursor-col
1622   var start-col/ecx: int <- copy *cursor-col
1623   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-function
1624   var word/eax: (addr word) <- lookup *word-ah?
1625   var cursor-index/eax: int <- cursor-index word
1626   start-col <- subtract cursor-index
1627   var cursor-row/ebx: (addr int) <- get sandbox, cursor-row
1628   move-cursor screen, *cursor-row, start-col
1629   var word/eax: (addr word) <- lookup *word-ah?
1630   print-word screen, word
1631 }
1632 
1633 # Render just the words in 'line'.
1634 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) {
1635   # curr-word
1636   var line/eax: (addr line) <- copy _line
1637   var first-word-ah/eax: (addr handle word) <- get line, data
1638   var _curr-word/eax: (addr word) <- lookup *first-word-ah
1639   var curr-word/esi: (addr word) <- copy _curr-word
1640   #
1641   # loop-carried dependency
1642   var curr-col/ecx: int <- copy left-col
1643   #
1644   {
1645     compare curr-word, 0
1646     break-if-=
1647 #?     print-string 0, "-- word in penultimate lines: "
1648 #?     {
1649 #?       var foo/eax: int <- copy curr-word
1650 #?       print-int32-hex 0, foo
1651 #?     }
1652 #?     print-string 0, "\n"
1653     var old-col/edx: int <- copy curr-col
1654     move-cursor screen, curr-row, curr-col
1655     print-word screen, curr-word
1656     {
1657       var max-width/eax: int <- word-length curr-word
1658       curr-col <- add max-width
1659       curr-col <- add 1/margin-right
1660     }
1661     # cache cursor column if necessary
1662     {
1663       compare curr-word, cursor-word
1664       break-if-!=
1665 #?       print-string 0, "Cursor at "
1666 #?       print-int32-decimal 0, curr-row
1667 #?       print-string 0, ", "
1668 #?       print-int32-decimal 0, old-col
1669 #?       print-string 0, "\n"
1670 #?       print-string 0, "contents: "
1671 #?       print-word 0, cursor-word
1672 #?       print-string 0, "\n"
1673 #?       {
1674 #?         var foo/eax: int <- copy cursor-word
1675 #?         print-int32-hex 0, foo
1676 #?         print-string 0, "\n"
1677 #?       }
1678       var dest/ecx: (addr int) <- copy cursor-row-addr
1679       var src/eax: int <- copy curr-row
1680       copy-to *dest, src
1681       dest <- copy cursor-col-addr
1682       copy-to *dest, old-col
1683       var cursor-index-in-word/eax: int <- cursor-index curr-word
1684       add-to *dest, cursor-index-in-word
1685     }
1686     # loop update
1687     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1688     var _curr-word/eax: (addr word) <- lookup *next-word-ah
1689     curr-word <- copy _curr-word
1690     loop
1691   }
1692 }
1693 
1694 fn call-depth-at-cursor _sandbox: (addr sandbox) -> _/eax: int {
1695   var sandbox/esi: (addr sandbox) <- copy _sandbox
1696   var cursor-call-path/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
1697   var result/eax: int <- call-path-element-length cursor-call-path
1698   result <- add 2  # input-row - 1
1699   return result
1700 }
1701 
1702 fn call-path-element-length _x: (addr handle call-path-element) -> _/eax: int {
1703   var curr-ah/ecx: (addr handle call-path-element) <- copy _x
1704   var result/edi: int <- copy 0
1705   {
1706     var curr/eax: (addr call-path-element) <- lookup *curr-ah
1707     compare curr, 0
1708     break-if-=
1709     curr-ah <- get curr, next
1710     result <- increment
1711     loop
1712   }
1713   return result
1714 }
1715 
1716 # Render the line of words in line, along with the state of the stack under each word.
1717 # Also render any expanded function calls using recursive calls.
1718 #
1719 # Along the way, compute the column the cursor should be positioned at (cursor-col-addr).
1720 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 {
1721 #?   print-string 0, "render-line\n"
1722 #?   dump-table bindings
1723   # curr-word
1724   var line/esi: (addr line) <- copy _line
1725   var first-word-ah/eax: (addr handle word) <- get line, data
1726   var curr-word/eax: (addr word) <- lookup *first-word-ah
1727   #
1728   # loop-carried dependency
1729   var curr-col/ecx: int <- copy left-col
1730   #
1731   {
1732     compare curr-word, 0
1733     break-if-=
1734 #?     print-string 0, "-- word "
1735 #?     print-word 0, curr-word
1736 #?     print-string 0, "\n"
1737     # if necessary, first render columns for subsidiary stack
1738     $render-line:subsidiary: {
1739       {
1740 #?         print-string 0, "check sub\n"
1741         var display-subsidiary-stack?/eax: boolean <- find-in-call-paths expanded-words, curr-path
1742         compare display-subsidiary-stack?, 0/false
1743         break-if-= $render-line:subsidiary
1744       }
1745 #?       print-string 0, "render subsidiary stack\n"
1746       # does function exist?
1747       var callee/edi: (addr function) <- copy 0
1748       {
1749         var callee-h: (handle function)
1750         var callee-ah/ecx: (addr handle function) <- address callee-h
1751         callee functions, curr-word, callee-ah
1752         var _callee/eax: (addr function) <- lookup *callee-ah
1753         callee <- copy _callee
1754         compare callee, 0
1755         break-if-= $render-line:subsidiary
1756       }
1757       move-cursor screen, top-row, curr-col
1758       start-color screen, 8, 7
1759       print-word screen, curr-word
1760       {
1761         var word-len/eax: int <- word-length curr-word
1762         curr-col <- add word-len
1763         curr-col <- add 2
1764         increment top-row
1765       }
1766       # obtain stack at call site
1767       var stack-storage: value-stack
1768       var stack/edx: (addr value-stack) <- address stack-storage
1769       initialize-value-stack stack, 0x10
1770       {
1771         var prev-word-ah/eax: (addr handle word) <- get curr-word, prev
1772         var prev-word/eax: (addr word) <- lookup *prev-word-ah
1773         compare prev-word, 0
1774         break-if-=
1775         var bindings2-storage: table
1776         var bindings2/ebx: (addr table) <- address bindings2-storage
1777         deep-copy-table bindings, bindings2
1778         evaluate functions, bindings2, first-line, prev-word, stack
1779       }
1780       # construct new bindings
1781       var callee-bindings-storage: table
1782       var callee-bindings/esi: (addr table) <- address callee-bindings-storage
1783       initialize-table callee-bindings, 0x10
1784       bind-args callee, stack, callee-bindings
1785       # obtain body
1786       var callee-body-ah/eax: (addr handle line) <- get callee, body
1787       var callee-body/eax: (addr line) <- lookup *callee-body-ah
1788       var callee-body-first-word/edx: (addr handle word) <- get callee-body, data
1789       # - render subsidiary stack
1790       push-to-call-path-element curr-path, callee-body-first-word  # leak
1791 #?       print-string 0, "subsidiary {\n"
1792 #?       dump-table callee-bindings
1793 #?       syscall_exit
1794       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
1795 #?       print-string 0, "}\n"
1796       drop-from-call-path-element curr-path
1797       #
1798       move-cursor screen, top-row, curr-col
1799       print-code-point screen, 0x21d7/⇗
1800       #
1801       curr-col <- add 2
1802       decrement top-row
1803     }
1804     # render main column
1805     var old-col/edx: int <- copy curr-col
1806     var bindings2-storage: table
1807     var bindings2/ebx: (addr table) <- address bindings2-storage
1808 #?     print-string 0, "deep-copy {\n"
1809     deep-copy-table bindings, bindings2
1810 #?     print-string 0, "}\n"
1811 #?     print-string 0, "render column {\n"
1812     curr-col <- render-column screen, functions, bindings2, first-line, line, curr-word, top-row, curr-col
1813 #?     print-string 0, "}\n"
1814     # cache cursor column if necessary
1815     $render-line:cache-cursor-column: {
1816       {
1817         var found?/eax: boolean <- call-path-element-match? curr-path, cursor-call-path
1818         compare found?, 0/false
1819         break-if-= $render-line:cache-cursor-column
1820       }
1821       var dest/edi: (addr int) <- copy cursor-row-addr
1822       {
1823         var src/eax: int <- copy top-row
1824         copy-to *dest, src
1825       }
1826       dest <- copy cursor-col-addr
1827       copy-to *dest, old-col
1828       var cursor-index-in-word/eax: int <- cursor-index curr-word
1829       add-to *dest, cursor-index-in-word
1830     }
1831     # loop update
1832 #?     print-string 0, "next word\n"
1833     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1834     curr-word <- lookup *next-word-ah
1835 #?     {
1836 #?       var foo/eax: int <- copy curr-word
1837 #?       print-int32-hex 0, foo
1838 #?       print-string 0, "\n"
1839 #?     }
1840     increment-final-element curr-path
1841     loop
1842   }
1843   return curr-col
1844 }
1845 
1846 fn callee functions: (addr handle function), word: (addr word), out: (addr handle function) {
1847   var stream-storage: (stream byte 0x10)
1848   var stream/esi: (addr stream byte) <- address stream-storage
1849   emit-word word, stream
1850   find-function functions, stream, out
1851 }
1852 
1853 # Render:
1854 #   - starting at top-row, left-col: final-word
1855 #   - starting somewhere below at left-col: the stack result from interpreting first-world to final-word (inclusive)
1856 #
1857 # Return the farthest column written.
1858 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 {
1859 #?   print-string 0, "render-column\n"
1860 #?   dump-table bindings
1861   var max-width/esi: int <- copy 0
1862   {
1863     # compute stack
1864     var stack: value-stack
1865     var stack-addr/edi: (addr value-stack) <- address stack
1866     initialize-value-stack stack-addr, 0x10/max-words
1867     # copy bindings
1868     var bindings2-storage: table
1869     var bindings2/ebx: (addr table) <- address bindings2-storage
1870 #?     print-string 0, "deep copy table {\n"
1871     deep-copy-table bindings, bindings2
1872 #?     print-string 0, "}\n"
1873     evaluate functions, bindings2, first-line, final-word, stack-addr
1874     # indent stack
1875     var indented-col/ebx: int <- copy left-col
1876     indented-col <- add 1/margin-right
1877     # render stack
1878     var curr-row/edx: int <- copy top-row
1879     curr-row <- add 2/stack-margin-top
1880     {
1881       var top-addr/ecx: (addr int) <- get stack-addr, top
1882       compare *top-addr, 0
1883       break-if-<=
1884       decrement *top-addr
1885       var data-ah/eax: (addr handle array value) <- get stack-addr, data
1886       var data/eax: (addr array value) <- lookup *data-ah
1887       var top/ecx: int <- copy *top-addr
1888       var dest-offset/ecx: (offset value) <- compute-offset data, top
1889       var val/eax: (addr value) <- index data, dest-offset
1890       render-value-at screen, curr-row, indented-col, val, 1/top-level=true
1891       {
1892         var width/eax: int <- value-width val, 1
1893         compare width, max-width
1894         break-if-<=
1895         max-width <- copy width
1896       }
1897       var height/eax: int <- value-height val
1898       curr-row <- add height
1899       loop
1900     }
1901   }
1902 
1903   max-width <- add 2  # spaces on either side of items on the stack
1904 
1905   # render word, initialize result
1906   reset-formatting screen
1907   move-cursor screen, top-row, left-col
1908   print-word screen, final-word
1909   {
1910     var width/eax: int <- word-length final-word
1911     compare width, max-width
1912     break-if-<=
1913     max-width <- copy width
1914   }
1915 
1916   # post-process right-col
1917   var right-col/ecx: int <- copy left-col
1918   right-col <- add max-width
1919   right-col <- add 1/margin-right
1920 #?   print-int32-decimal 0, left-col
1921 #?   print-string 0, " => "
1922 #?   print-int32-decimal 0, right-col
1923 #?   print-string 0, "\n"
1924   return right-col
1925 }
1926 
1927 fn render-function-menu _env: (addr environment) {
1928   var env/esi: (addr environment) <- copy _env
1929   var screen-ah/edi: (addr handle screen) <- get env, screen
1930   var _screen/eax: (addr screen) <- lookup *screen-ah
1931   var screen/edi: (addr screen) <- copy _screen
1932   var nrows/eax: (addr int) <- get env, nrows
1933   move-cursor screen, *nrows, 0
1934   start-reverse-video screen
1935   print-string screen, " ctrl-q "
1936   reset-formatting screen
1937   print-string screen, " quit     "
1938   start-reverse-video screen
1939   print-string screen, " ctrl-a "
1940   reset-formatting screen
1941   print-string screen, " ⏮   "
1942   start-reverse-video screen
1943   print-string screen, " ctrl-b "
1944   reset-formatting screen
1945   print-string screen, " ◀ word  "
1946   start-reverse-video screen
1947   print-string screen, " ctrl-f "
1948   reset-formatting screen
1949   print-string screen, " word ▶  "
1950   start-reverse-video screen
1951   print-string screen, " ctrl-e "
1952   reset-formatting screen
1953   print-string screen, " ⏭       "
1954   start-reverse-video screen
1955   print-string screen, " ctrl-g "
1956   reset-formatting screen
1957   print-string screen, " go to function  "
1958   start-reverse-video screen
1959   print-string screen, " tab "
1960   reset-formatting screen
1961   print-string screen, " go to sandbox"
1962 }
1963 
1964 fn render-sandbox-menu _env: (addr environment) {
1965   var env/esi: (addr environment) <- copy _env
1966   var screen-ah/edi: (addr handle screen) <- get env, screen
1967   var _screen/eax: (addr screen) <- lookup *screen-ah
1968   var screen/edi: (addr screen) <- copy _screen
1969   var nrows/eax: (addr int) <- get env, nrows
1970   move-cursor screen, *nrows, 0
1971   start-reverse-video screen
1972   print-string screen, " ctrl-q "
1973   reset-formatting screen
1974   print-string screen, " quit     "
1975   start-reverse-video screen
1976   print-string screen, " ctrl-a "
1977   reset-formatting screen
1978   print-string screen, " ⏮   "
1979   start-reverse-video screen
1980   print-string screen, " ctrl-b "
1981   reset-formatting screen
1982   print-string screen, " ◀ word  "
1983   start-reverse-video screen
1984   print-string screen, " ctrl-f "
1985   reset-formatting screen
1986   print-string screen, " word ▶  "
1987   start-reverse-video screen
1988   print-string screen, " ctrl-e "
1989   reset-formatting screen
1990   print-string screen, " ⏭       "
1991   start-reverse-video screen
1992   print-string screen, " ctrl-g "
1993   reset-formatting screen
1994   print-string screen, " go to function     "
1995   start-reverse-video screen
1996   print-string screen, " ctrl-l "
1997   reset-formatting screen
1998   print-string screen, " new line "
1999   start-reverse-video screen
2000   print-string screen, " ctrl-u "
2001   reset-formatting screen
2002   print-string screen, " clear  "
2003   start-reverse-video screen
2004   print-string screen, " ctrl-n "
2005   reset-formatting screen
2006   print-string screen, " name word  "
2007   start-reverse-video screen
2008   print-string screen, " ctrl-d "
2009   reset-formatting screen
2010   print-string screen, " define function"
2011 }
2012 
2013 fn clear-canvas _env: (addr environment) {
2014   var env/esi: (addr environment) <- copy _env
2015   var screen-ah/edi: (addr handle screen) <- get env, screen
2016   var _screen/eax: (addr screen) <- lookup *screen-ah
2017   var screen/edi: (addr screen) <- copy _screen
2018   clear-screen screen
2019   var nrows/eax: (addr int) <- get env, nrows
2020   var sep-col/ecx: (addr int) <- get env, code-separator-col
2021   # divider
2022   draw-vertical-line screen, 1, *nrows, *sep-col
2023   # primitives
2024   var dummy/eax: int <- render-primitives screen, *nrows, *sep-col
2025 }
2026 
2027 # return value: top-most row written to
2028 fn render-primitives screen: (addr screen), bottom-margin-row: int, right-col: int -> _/eax: int {
2029   # render primitives from the bottom of the screen upward
2030   var row/ecx: int <- copy bottom-margin-row
2031   row <- subtract 1
2032   var col/edx: int <- copy 1
2033   move-cursor screen, row, col
2034   row, col <- render-primitive-group screen, row, col, right-col, "numbers: ", "+ - * / sqrt  "
2035   row, col <- render-primitive-group screen, row, col, right-col, "arrays: ", "len  "
2036   row, col <- render-primitive-group screen, row, col, right-col, "files: ", "open read slurp lines  "
2037   row, col <- render-primitive-group screen, row, col, right-col, "misc: ", "dup swap  "  # hack: keep these at the right of the bottom row
2038   row, col <- render-primitive-group screen, row, col, right-col, "screens: ", "fake-screen print move up down left right  "
2039   # finally print heading up top
2040   row <- decrement
2041   move-cursor screen, row, 1
2042   start-bold screen
2043   print-string screen, "primitives:"
2044   reset-formatting screen
2045   return row
2046 }
2047 
2048 # start at row, col and print the given strings
2049 # move up one row if there isn't enough room before right-col
2050 # return row, col printed until
2051 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 {
2052   var row/ecx: int <- copy _row
2053   var col/edx: int <- copy _col
2054   # decrement row if necessary
2055   var new-col/ebx: int <- copy col
2056   var heading/esi: (addr array byte) <- copy _heading
2057   var len1/eax: int <- length heading
2058   new-col <- add len1
2059   var contents/edi: (addr array byte) <- copy _contents
2060   var len2/eax: int <- length contents
2061   new-col <- add len2
2062   var bound/eax: int <- copy right-col
2063   bound <- decrement
2064   {
2065     compare new-col, bound
2066     break-if-<=
2067     row <- decrement
2068     col <- copy 1
2069   }
2070   move-cursor screen, row, col
2071   start-color screen, 0xf6, 7
2072   print-string screen, heading
2073   reset-formatting screen
2074   print-string screen, contents
2075   return row, new-col
2076 }
2077 
2078 fn render-functions screen: (addr screen), right-col: int, _env: (addr environment) {
2079   var row/ecx: int <- copy 1
2080   var dummy-col/edx: int <- copy right-col
2081   var env/esi: (addr environment) <- copy _env
2082   var functions/esi: (addr handle function) <- get env, functions
2083   {
2084     var curr/eax: (addr function) <- lookup *functions
2085     compare curr, 0
2086     break-if-=
2087     row, dummy-col <- render-function-right-aligned screen, row, right-col, curr
2088     functions <- get curr, next
2089     row <- add 1/inter-function-margin
2090     loop
2091   }
2092 }
2093 
2094 # print function starting at row, right-aligned before right-col
2095 # return row, col printed until
2096 fn render-function-right-aligned screen: (addr screen), row: int, right-col: int, f: (addr function) -> _/ecx: int, _/edx: int {
2097   var col/edx: int <- copy right-col
2098   col <- subtract 1/function-right-margin
2099   var col2/ebx: int <- copy col
2100   var width/eax: int <- function-width f
2101   col <- subtract width
2102   var new-row/ecx: int <- copy row
2103   var height/eax: int <- function-height f
2104   new-row <- add height
2105   new-row <- decrement
2106   col <- subtract 1/function-left-padding
2107   start-color screen, 0, 0xf7
2108   clear-rect screen, row, col, new-row, col2
2109   col <- add 1
2110 #?   var dummy/eax: grapheme <- read-key-from-real-keyboard
2111   render-function screen, row, col, f
2112   new-row <- add 1/function-bottom-margin
2113   col <- subtract 1/function-left-padding
2114   col <- subtract 1/function-left-margin
2115   reset-formatting screen
2116   return new-row, col
2117 }
2118 
2119 # render function starting at row, col
2120 # only single-line functions supported for now
2121 fn render-function screen: (addr screen), row: int, col: int, _f: (addr function) {
2122   var f/esi: (addr function) <- copy _f
2123   var args/ecx: (addr handle word) <- get f, args
2124   move-cursor screen, row, col
2125   print-words-in-reverse screen, args
2126   var name-ah/eax: (addr handle array byte) <- get f, name
2127   var name/eax: (addr array byte) <- lookup *name-ah
2128   start-bold screen
2129   print-string screen, name
2130   reset-formatting screen
2131   start-color screen, 0, 0xf7
2132   increment row
2133   add-to col, 2
2134   move-cursor screen, row, col
2135   print-string screen, "≡ "
2136   add-to col, 2
2137   var cursor-row/ecx: (addr int) <- get f, cursor-row
2138   var cursor-col/edx: (addr int) <- get f, cursor-col
2139   var cursor-word-ah/eax: (addr handle word) <- get f, cursor-word
2140   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
2141   var cursor-word/ebx: (addr word) <- copy _cursor-word
2142   var body-ah/eax: (addr handle line) <- get f, body
2143   var body/eax: (addr line) <- lookup *body-ah
2144   render-line-without-stack screen, body, row, col, cursor-word, cursor-row, cursor-col
2145 }
2146 
2147 fn real-grapheme? g: grapheme -> _/eax: boolean {
2148   # if g == newline return true
2149   compare g, 0xa
2150   {
2151     break-if-!=
2152     return 1/true
2153   }
2154   # if g == tab return true
2155   compare g, 9
2156   {
2157     break-if-!=
2158     return 1/true
2159   }
2160   # if g < 32 return false
2161   compare g, 0x20
2162   {
2163     break-if->=
2164     return 0/false
2165   }
2166   # if g <= 255 return true
2167   compare g, 0xff
2168   {
2169     break-if->
2170     return 1/true
2171   }
2172   # if (g&0xff == Esc) it's an escape sequence
2173   and-with g, 0xff
2174   compare g, 0x1b/esc
2175   {
2176     break-if-!=
2177     return 0/false
2178   }
2179   # otherwise return true
2180   return 1/true
2181 }