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