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     ## strings/arrays
 781     # if w == "len" return true
 782     subresult <- word-equal? w, "len"
 783     compare subresult, 0  # false
 784     break-if-!=
 785     ## files
 786     # if w == "open" return true
 787     subresult <- word-equal? w, "open"
 788     compare subresult, 0  # false
 789     break-if-!=
 790     # if w == "read" return true
 791     subresult <- word-equal? w, "read"
 792     compare subresult, 0  # false
 793     break-if-!=
 794     # if w == "slurp" return true
 795     subresult <- word-equal? w, "slurp"
 796     compare subresult, 0  # false
 797     break-if-!=
 798     # if w == "lines" return true
 799     subresult <- word-equal? w, "lines"
 800     compare subresult, 0  # false
 801     break-if-!=
 802     ## screens
 803     # if w == "fake-screen" return true
 804     subresult <- word-equal? w, "fake-screen"
 805     compare subresult, 0  # false
 806     break-if-!=
 807     # if w == "print" return true
 808     subresult <- word-equal? w, "print"
 809     compare subresult, 0  # false
 810     break-if-!=
 811     # if w == "move" return true
 812     subresult <- word-equal? w, "move"
 813     compare subresult, 0  # false
 814     break-if-!=
 815     # if w == "up" return true
 816     subresult <- word-equal? w, "up"
 817     compare subresult, 0  # false
 818     break-if-!=
 819     # if w == "down" return true
 820     subresult <- word-equal? w, "down"
 821     compare subresult, 0  # false
 822     break-if-!=
 823     # if w == "left" return true
 824     subresult <- word-equal? w, "left"
 825     compare subresult, 0  # false
 826     break-if-!=
 827     # if w == "right" return true
 828     subresult <- word-equal? w, "right"
 829     compare subresult, 0  # false
 830     break-if-!=
 831     ## hacks
 832     # if w == "dup" return true
 833     subresult <- word-equal? w, "dup"
 834     compare subresult, 0  # false
 835     break-if-!=
 836     # if w == "swap" return true
 837     subresult <- word-equal? w, "swap"
 838     compare subresult, 0  # false
 839     break-if-!=
 840     # return w in functions
 841     var out-h: (handle function)
 842     var out/eax: (addr handle function) <- address out-h
 843     callee functions-ah, w, out
 844     var found?/eax: (addr function) <- lookup *out
 845     result <- copy found?
 846   }
 847   return result
 848 }
 849 
 850 fn arg-exists? _f-ah: (addr handle function), arg: (addr word) -> _/ebx: boolean {
 851   var f-ah/eax: (addr handle function) <- copy _f-ah
 852   var f/eax: (addr function) <- lookup *f-ah
 853   var args-ah/eax: (addr handle word) <- get f, args
 854   var result/ebx: boolean <- word-exists? args-ah, arg
 855   return result
 856 }
 857 
 858 # construct a call to `f` with copies of exactly its args
 859 fn construct-call _f-ah: (addr handle function), _dest-ah: (addr handle word) {
 860   var f-ah/eax: (addr handle function) <- copy _f-ah
 861   var _f/eax: (addr function) <- lookup *f-ah
 862   var f/esi: (addr function) <- copy _f
 863   # append args in reverse
 864   var args-ah/eax: (addr handle word) <- get f, args
 865   var dest-ah/edi: (addr handle word) <- copy _dest-ah
 866   copy-words-in-reverse args-ah, dest-ah
 867   # append name
 868   var name-ah/eax: (addr handle array byte) <- get f, name
 869   var name/eax: (addr array byte) <- lookup *name-ah
 870   append-word-at-end-with dest-ah, name
 871 }
 872 
 873 fn word-index _words: (addr handle word), _n: int, out: (addr handle word) {
 874   var n/ecx: int <- copy _n
 875   {
 876     compare n, 0
 877     break-if-!=
 878     copy-object _words, out
 879     return
 880   }
 881   var words-ah/eax: (addr handle word) <- copy _words
 882   var words/eax: (addr word) <- lookup *words-ah
 883   var next/eax: (addr handle word) <- get words, next
 884   n <- decrement
 885   word-index next, n, out
 886 }
 887 
 888 fn toggle-cursor-word _sandbox: (addr sandbox) {
 889   var sandbox/esi: (addr sandbox) <- copy _sandbox
 890   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
 891   var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path
 892 #?   print-string 0, "cursor call path: "
 893 #?   dump-call-path-element 0, cursor-call-path
 894 #?   print-string 0, "expanded words:\n"
 895 #?   dump-call-paths 0, expanded-words
 896   var already-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path
 897   compare already-expanded?, 0  # false
 898   {
 899     break-if-!=
 900 #?     print-string 0, "expand\n"
 901     # if not already-expanded, insert
 902     insert-in-call-path expanded-words cursor-call-path
 903 #?     print-string 0, "expanded words now:\n"
 904 #?     dump-call-paths 0, expanded-words
 905     return
 906   }
 907   {
 908     break-if-=
 909     # otherwise delete
 910     delete-in-call-path expanded-words cursor-call-path
 911   }
 912 }
 913 
 914 fn append-line _sandbox: (addr sandbox) {
 915   var sandbox/esi: (addr sandbox) <- copy _sandbox
 916   var line-ah/ecx: (addr handle line) <- get sandbox, data
 917   {
 918     var line/eax: (addr line) <- lookup *line-ah
 919     var next-line-ah/edx: (addr handle line) <- get line, next
 920     var next-line/eax: (addr line) <- lookup *next-line-ah
 921     compare next-line, 0
 922     break-if-=
 923     line-ah <- copy next-line-ah
 924     loop
 925   }
 926   var line/eax: (addr line) <- lookup *line-ah
 927   var final-line-ah/edx: (addr handle line) <- get line, next
 928   allocate final-line-ah
 929   var final-line/eax: (addr line) <- lookup *final-line-ah
 930   initialize-line final-line
 931   var final-prev/eax: (addr handle line) <- get final-line, prev
 932   copy-object line-ah, final-prev
 933   # clear cursor
 934   var final-line/eax: (addr line) <- lookup *final-line-ah
 935   var word-ah/ecx: (addr handle word) <- get final-line, data
 936   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
 937   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
 938   var dest/eax: (addr handle word) <- get cursor-call-path, word
 939   copy-object word-ah, dest
 940 }
 941 
 942 #############
 943 # Visualize
 944 #############
 945 
 946 fn evaluate-environment _env: (addr environment), stack: (addr value-stack) {
 947   var env/esi: (addr environment) <- copy _env
 948   # functions
 949   var functions/edx: (addr handle function) <- get env, functions
 950   # line
 951   var sandbox-ah/esi: (addr handle sandbox) <- get env, sandboxes
 952   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
 953   var line-ah/eax: (addr handle line) <- get sandbox, data
 954   var _line/eax: (addr line) <- lookup *line-ah
 955   var line/esi: (addr line) <- copy _line
 956   evaluate functions, 0, line, 0, stack
 957 }
 958 
 959 fn render _env: (addr environment) {
 960 #?   print-string 0, "== render\n"
 961   var env/esi: (addr environment) <- copy _env
 962   clear-canvas env
 963   # screen
 964   var screen-ah/eax: (addr handle screen) <- get env, screen
 965   var _screen/eax: (addr screen) <- lookup *screen-ah
 966   var screen/edi: (addr screen) <- copy _screen
 967   # repl-col
 968   var _repl-col/eax: (addr int) <- get env, code-separator-col
 969   var repl-col/ecx: int <- copy *_repl-col
 970   repl-col <- add 2  # repl-margin-left
 971   # functions
 972   var functions/edx: (addr handle function) <- get env, functions
 973   # sandbox
 974   var sandbox-ah/eax: (addr handle sandbox) <- get env, sandboxes
 975   var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah
 976   # bindings
 977   var bindings-storage: table
 978   var bindings/ebx: (addr table) <- address bindings-storage
 979   initialize-table bindings, 0x10
 980 #?   print-string 0, "render-sandbox {\n"
 981   render-sandbox screen, functions, bindings, sandbox, 3, repl-col
 982 #?   print-string 0, "render-sandbox }\n"
 983 }
 984 
 985 fn render-sandbox screen: (addr screen), functions: (addr handle function), bindings: (addr table), _sandbox: (addr sandbox), top-row: int, left-col: int {
 986   var sandbox/esi: (addr sandbox) <- copy _sandbox
 987   # line
 988   var curr-line-ah/eax: (addr handle line) <- get sandbox, data
 989   var _curr-line/eax: (addr line) <- lookup *curr-line-ah
 990   var curr-line/ecx: (addr line) <- copy _curr-line
 991   #
 992   var curr-row/edx: int <- copy top-row
 993   # cursor row, col
 994   var cursor-row: int
 995   var cursor-row-addr: (addr int)
 996   var tmp/eax: (addr int) <- address cursor-row
 997   copy-to cursor-row-addr, tmp
 998   var cursor-col: int
 999   var cursor-col-addr: (addr int)
1000   tmp <- address cursor-col
1001   copy-to cursor-col-addr, tmp
1002   # render all but final line without stack
1003 #?   print-string 0, "render all but final line\n"
1004   {
1005     var next-line-ah/eax: (addr handle line) <- get curr-line, next
1006     var next-line/eax: (addr line) <- lookup *next-line-ah
1007     compare next-line, 0
1008     break-if-=
1009     {
1010       var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1011       var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1012       var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1013       var cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1014       # 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
1015       render-line-without-stack screen, curr-line, curr-row, left-col, cursor-word, cursor-row-addr, cursor-col-addr
1016     }
1017     curr-line <- copy next-line
1018     curr-row <- add 2
1019     loop
1020   }
1021   #
1022   render-final-line-with-stack screen, functions, bindings, sandbox, curr-row, left-col, cursor-row-addr, cursor-col-addr
1023   # at most one of the following dialogs will be rendered
1024   render-rename-dialog screen, sandbox, cursor-row, cursor-col
1025   render-define-dialog screen, sandbox, cursor-row, cursor-col
1026   move-cursor screen, cursor-row, cursor-col
1027 }
1028 
1029 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) {
1030   var sandbox/esi: (addr sandbox) <- copy _sandbox
1031   # expanded-words
1032   var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words
1033   # cursor-word
1034   var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1035   var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah
1036   var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word
1037   var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah
1038   var cursor-word/ebx: (addr word) <- copy _cursor-word
1039 #?   print-string 0, "word at cursor: "
1040 #?   print-word 0, cursor-word
1041 #?   print-string 0, "\n"
1042   # cursor-call-path
1043   var cursor-call-path: (addr handle call-path-element)
1044   {
1045     var src/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path
1046     copy-to cursor-call-path, src
1047   }
1048   # first line
1049   var first-line-ah/eax: (addr handle line) <- get sandbox, data
1050   var _first-line/eax: (addr line) <- lookup *first-line-ah
1051   var first-line/edx: (addr line) <- copy _first-line
1052   # final line
1053   var final-line-storage: (handle line)
1054   var final-line-ah/eax: (addr handle line) <- address final-line-storage
1055   final-line sandbox, final-line-ah
1056   var final-line/eax: (addr line) <- lookup *final-line-ah
1057   # curr-path
1058   var curr-path-storage: (handle call-path-element)
1059   var curr-path/ecx: (addr handle call-path-element) <- address curr-path-storage
1060   allocate curr-path  # leak
1061   initialize-path-from-line final-line, curr-path
1062   #
1063   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
1064 }
1065 
1066 fn final-line _sandbox: (addr sandbox), out: (addr handle line) {
1067   var sandbox/esi: (addr sandbox) <- copy _sandbox
1068   var curr-line-ah/ecx: (addr handle line) <- get sandbox, data
1069   {
1070     var curr-line/eax: (addr line) <- lookup *curr-line-ah
1071     var next-line-ah/edx: (addr handle line) <- get curr-line, next
1072     var next-line/eax: (addr line) <- lookup *next-line-ah
1073     compare next-line, 0
1074     break-if-=
1075     curr-line-ah <- copy next-line-ah
1076     loop
1077   }
1078   copy-object curr-line-ah, out
1079 }
1080 
1081 fn render-rename-dialog screen: (addr screen), _sandbox: (addr sandbox), cursor-row: int, cursor-col: int {
1082   var sandbox/edi: (addr sandbox) <- copy _sandbox
1083   var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1084   var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah?
1085   compare rename-word-mode?, 0
1086   break-if-=
1087   # clear a space for the dialog
1088   var top-row/eax: int <- copy cursor-row
1089   top-row <- subtract 3
1090   var bottom-row/ecx: int <- copy cursor-row
1091   bottom-row <- add 3
1092   var left-col/edx: int <- copy cursor-col
1093   left-col <- subtract 0x10
1094   var right-col/ebx: int <- copy cursor-col
1095   right-col <- add 0x10
1096   clear-rect screen, top-row, left-col, bottom-row, right-col
1097   draw-box screen, top-row, left-col, bottom-row, right-col
1098   # render a little menu for the dialog
1099   var menu-row/ecx: int <- copy bottom-row
1100   menu-row <- decrement
1101   var menu-col/edx: int <- copy left-col
1102   menu-col <- add 2
1103   move-cursor screen, menu-row, menu-col
1104   start-reverse-video screen
1105   print-string screen, " esc "
1106   reset-formatting screen
1107   print-string screen, " cancel  "
1108   start-reverse-video screen
1109   print-string screen, " enter "
1110   reset-formatting screen
1111   print-string screen, " rename  "
1112   # draw the word, positioned appropriately around the cursor
1113   var start-col/ecx: int <- copy cursor-col
1114   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-cursor-word
1115   var word/eax: (addr word) <- lookup *word-ah?
1116   var cursor-index/eax: int <- cursor-index word
1117   start-col <- subtract cursor-index
1118   move-cursor screen, cursor-row, start-col
1119   var word/eax: (addr word) <- lookup *word-ah?
1120   print-word screen, word
1121 }
1122 
1123 fn render-define-dialog screen: (addr screen), _sandbox: (addr sandbox), cursor-row: int, cursor-col: int {
1124   var sandbox/edi: (addr sandbox) <- copy _sandbox
1125   var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function
1126   var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah?
1127   compare define-function-mode?, 0
1128   break-if-=
1129   # clear a space for the dialog
1130   var top-row/eax: int <- copy cursor-row
1131   top-row <- subtract 3
1132   var bottom-row/ecx: int <- copy cursor-row
1133   bottom-row <- add 3
1134   var left-col/edx: int <- copy cursor-col
1135   left-col <- subtract 0x10
1136   var right-col/ebx: int <- copy cursor-col
1137   right-col <- add 0x10
1138   clear-rect screen, top-row, left-col, bottom-row, right-col
1139   draw-box screen, top-row, left-col, bottom-row, right-col
1140   # render a little menu for the dialog
1141   var menu-row/ecx: int <- copy bottom-row
1142   menu-row <- decrement
1143   var menu-col/edx: int <- copy left-col
1144   menu-col <- add 2
1145   move-cursor screen, menu-row, menu-col
1146   start-reverse-video screen
1147   print-string screen, " esc "
1148   reset-formatting screen
1149   print-string screen, " cancel  "
1150   start-reverse-video screen
1151   print-string screen, " enter "
1152   reset-formatting screen
1153   print-string screen, " define  "
1154   # draw the word, positioned appropriately around the cursor
1155   var start-col/ecx: int <- copy cursor-col
1156   var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-function
1157   var word/eax: (addr word) <- lookup *word-ah?
1158   var cursor-index/eax: int <- cursor-index word
1159   start-col <- subtract cursor-index
1160   move-cursor screen, cursor-row, start-col
1161   var word/eax: (addr word) <- lookup *word-ah?
1162   print-word screen, word
1163 }
1164 
1165 # Render just the words in 'line'.
1166 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) {
1167   # curr-word
1168   var line/eax: (addr line) <- copy _line
1169   var first-word-ah/eax: (addr handle word) <- get line, data
1170   var _curr-word/eax: (addr word) <- lookup *first-word-ah
1171   var curr-word/esi: (addr word) <- copy _curr-word
1172   #
1173   # loop-carried dependency
1174   var curr-col/ecx: int <- copy left-col
1175   #
1176   {
1177     compare curr-word, 0
1178     break-if-=
1179 #?     print-string 0, "-- word in penultimate lines: "
1180 #?     {
1181 #?       var foo/eax: int <- copy curr-word
1182 #?       print-int32-hex 0, foo
1183 #?     }
1184 #?     print-string 0, "\n"
1185     var old-col/edx: int <- copy curr-col
1186     reset-formatting screen
1187     move-cursor screen, curr-row, curr-col
1188     print-word screen, curr-word
1189     {
1190       var max-width/eax: int <- word-length curr-word
1191       curr-col <- add max-width
1192       curr-col <- add 1  # margin-right
1193     }
1194     # cache cursor column if necessary
1195     {
1196       compare curr-word, cursor-word
1197       break-if-!=
1198 #?       print-string 0, "Cursor at "
1199 #?       print-int32-decimal 0, curr-row
1200 #?       print-string 0, ", "
1201 #?       print-int32-decimal 0, old-col
1202 #?       print-string 0, "\n"
1203 #?       print-string 0, "contents: "
1204 #?       print-word 0, cursor-word
1205 #?       print-string 0, "\n"
1206 #?       {
1207 #?         var foo/eax: int <- copy cursor-word
1208 #?         print-int32-hex 0, foo
1209 #?         print-string 0, "\n"
1210 #?       }
1211       var dest/ecx: (addr int) <- copy cursor-row-addr
1212       var src/eax: int <- copy curr-row
1213       copy-to *dest, src
1214       dest <- copy cursor-col-addr
1215       copy-to *dest, old-col
1216       var cursor-index-in-word/eax: int <- cursor-index curr-word
1217       add-to *dest, cursor-index-in-word
1218     }
1219     # loop update
1220     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1221     var _curr-word/eax: (addr word) <- lookup *next-word-ah
1222     curr-word <- copy _curr-word
1223     loop
1224   }
1225 }
1226 
1227 fn call-depth-at-cursor _sandbox: (addr sandbox) -> _/eax: int {
1228   var sandbox/esi: (addr sandbox) <- copy _sandbox
1229   var cursor-call-path/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path
1230   var result/eax: int <- call-path-element-length cursor-call-path
1231   result <- add 2  # input-row - 1
1232   return result
1233 }
1234 
1235 fn call-path-element-length _x: (addr handle call-path-element) -> _/eax: int {
1236   var curr-ah/ecx: (addr handle call-path-element) <- copy _x
1237   var result/edi: int <- copy 0
1238   {
1239     var curr/eax: (addr call-path-element) <- lookup *curr-ah
1240     compare curr, 0
1241     break-if-=
1242     curr-ah <- get curr, next
1243     result <- increment
1244     loop
1245   }
1246   return result
1247 }
1248 
1249 # Render the line of words in line, along with the state of the stack under each word.
1250 # Also render any expanded function calls using recursive calls.
1251 #
1252 # Along the way, compute the column the cursor should be positioned at (cursor-col-addr).
1253 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 {
1254 #?   print-string 0, "render-line\n"
1255 #?   dump-table bindings
1256   # curr-word
1257   var line/esi: (addr line) <- copy _line
1258   var first-word-ah/eax: (addr handle word) <- get line, data
1259   var curr-word/eax: (addr word) <- lookup *first-word-ah
1260   #
1261   # loop-carried dependency
1262   var curr-col/ecx: int <- copy left-col
1263   #
1264   {
1265     compare curr-word, 0
1266     break-if-=
1267 #?     print-string 0, "-- word "
1268 #?     print-word 0, curr-word
1269 #?     print-string 0, "\n"
1270     # if necessary, first render columns for subsidiary stack
1271     $render-line:subsidiary: {
1272       {
1273 #?         print-string 0, "check sub\n"
1274         var display-subsidiary-stack?/eax: boolean <- find-in-call-paths expanded-words, curr-path
1275         compare display-subsidiary-stack?, 0  # false
1276         break-if-= $render-line:subsidiary
1277       }
1278 #?       print-string 0, "render subsidiary stack\n"
1279       # does function exist?
1280       var callee/edi: (addr function) <- copy 0
1281       {
1282         var callee-h: (handle function)
1283         var callee-ah/ecx: (addr handle function) <- address callee-h
1284         callee functions, curr-word, callee-ah
1285         var _callee/eax: (addr function) <- lookup *callee-ah
1286         callee <- copy _callee
1287         compare callee, 0
1288         break-if-= $render-line:subsidiary
1289       }
1290       move-cursor screen, top-row, curr-col
1291       start-color screen, 8, 7
1292       print-word screen, curr-word
1293       {
1294         var word-len/eax: int <- word-length curr-word
1295         curr-col <- add word-len
1296         curr-col <- add 2
1297         increment top-row
1298       }
1299       # obtain stack at call site
1300       var stack-storage: value-stack
1301       var stack/edx: (addr value-stack) <- address stack-storage
1302       initialize-value-stack stack, 0x10
1303       {
1304         var prev-word-ah/eax: (addr handle word) <- get curr-word, prev
1305         var prev-word/eax: (addr word) <- lookup *prev-word-ah
1306         compare prev-word, 0
1307         break-if-=
1308         var bindings2-storage: table
1309         var bindings2/ebx: (addr table) <- address bindings2-storage
1310         deep-copy-table bindings, bindings2
1311         evaluate functions, bindings2, first-line, prev-word, stack
1312       }
1313       # construct new bindings
1314       var callee-bindings-storage: table
1315       var callee-bindings/esi: (addr table) <- address callee-bindings-storage
1316       initialize-table callee-bindings, 0x10
1317       bind-args callee, stack, callee-bindings
1318       # obtain body
1319       var callee-body-ah/eax: (addr handle line) <- get callee, body
1320       var callee-body/eax: (addr line) <- lookup *callee-body-ah
1321       var callee-body-first-word/edx: (addr handle word) <- get callee-body, data
1322       # - render subsidiary stack
1323       push-to-call-path-element curr-path, callee-body-first-word  # leak
1324 #?       print-string 0, "subsidiary {\n"
1325 #?       dump-table callee-bindings
1326 #?       syscall_exit
1327       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
1328 #?       print-string 0, "}\n"
1329       drop-from-call-path-element curr-path
1330       #
1331       move-cursor screen, top-row, curr-col
1332       print-code-point screen, 0x21d7  # ⇗
1333       #
1334       curr-col <- add 2
1335       decrement top-row
1336     }
1337     # render main column
1338     var old-col/edx: int <- copy curr-col
1339     var bindings2-storage: table
1340     var bindings2/ebx: (addr table) <- address bindings2-storage
1341 #?     print-string 0, "deep-copy {\n"
1342     deep-copy-table bindings, bindings2
1343 #?     print-string 0, "}\n"
1344 #?     print-string 0, "render column {\n"
1345     curr-col <- render-column screen, functions, bindings2, first-line, line, curr-word, top-row, curr-col
1346 #?     print-string 0, "}\n"
1347     # cache cursor column if necessary
1348     $render-line:cache-cursor-column: {
1349       {
1350         var found?/eax: boolean <- call-path-element-match? curr-path, cursor-call-path
1351         compare found?, 0  # false
1352         break-if-= $render-line:cache-cursor-column
1353       }
1354       var dest/edi: (addr int) <- copy cursor-row-addr
1355       {
1356         var src/eax: int <- copy top-row
1357         copy-to *dest, src
1358       }
1359       dest <- copy cursor-col-addr
1360       copy-to *dest, old-col
1361       var cursor-index-in-word/eax: int <- cursor-index curr-word
1362       add-to *dest, cursor-index-in-word
1363     }
1364     # loop update
1365 #?     print-string 0, "next word\n"
1366     var next-word-ah/edx: (addr handle word) <- get curr-word, next
1367     curr-word <- lookup *next-word-ah
1368 #?     {
1369 #?       var foo/eax: int <- copy curr-word
1370 #?       print-int32-hex 0, foo
1371 #?       print-string 0, "\n"
1372 #?     }
1373     increment-final-element curr-path
1374     loop
1375   }
1376   return curr-col
1377 }
1378 
1379 fn callee functions: (addr handle function), word: (addr word), out: (addr handle function) {
1380   var stream-storage: (stream byte 0x10)
1381   var stream/esi: (addr stream byte) <- address stream-storage
1382   emit-word word, stream
1383   find-function functions, stream, out
1384 }
1385 
1386 # Render:
1387 #   - starting at top-row, left-col: final-word
1388 #   - starting somewhere below at left-col: the stack result from interpreting first-world to final-word (inclusive)
1389 #
1390 # Return the farthest column written.
1391 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 {
1392 #?   print-string 0, "render-column\n"
1393 #?   dump-table bindings
1394   var max-width/esi: int <- copy 0
1395   {
1396     # compute stack
1397     var stack: value-stack
1398     var stack-addr/edi: (addr value-stack) <- address stack
1399     initialize-value-stack stack-addr, 0x10  # max-words
1400     # copy bindings
1401     var bindings2-storage: table
1402     var bindings2/ebx: (addr table) <- address bindings2-storage
1403 #?     print-string 0, "deep copy table {\n"
1404     deep-copy-table bindings, bindings2
1405 #?     print-string 0, "}\n"
1406     evaluate functions, bindings2, first-line, final-word, stack-addr
1407     # indent stack
1408     var indented-col/ebx: int <- copy left-col
1409     indented-col <- add 1  # margin-right
1410     # render stack
1411     var curr-row/edx: int <- copy top-row
1412     curr-row <- add 2  # stack-margin-top
1413     var _max-width/eax: int <- value-stack-max-width stack-addr
1414     max-width <- copy _max-width
1415     {
1416       var top-addr/ecx: (addr int) <- get stack-addr, top
1417       compare *top-addr, 0
1418       break-if-<=
1419       decrement *top-addr
1420       var data-ah/eax: (addr handle array value) <- get stack-addr, data
1421       var data/eax: (addr array value) <- lookup *data-ah
1422       var top/ecx: int <- copy *top-addr
1423       var dest-offset/ecx: (offset value) <- compute-offset data, top
1424       var val/eax: (addr value) <- index data, dest-offset
1425       render-value-at screen, curr-row, indented-col, val, max-width
1426       var height/eax: int <- value-height val
1427       curr-row <- add height
1428       loop
1429     }
1430   }
1431 
1432   max-width <- add 2  # spaces on either side of items on the stack
1433 
1434   # render word, initialize result
1435   reset-formatting screen
1436   move-cursor screen, top-row, left-col
1437   print-word screen, final-word
1438   {
1439     var size/eax: int <- word-length final-word
1440     compare size, max-width
1441     break-if-<=
1442     max-width <- copy size
1443   }
1444 
1445   # post-process right-col
1446   var right-col/ecx: int <- copy left-col
1447   right-col <- add max-width
1448   right-col <- add 1  # margin-right
1449 #?   print-int32-decimal 0, left-col
1450 #?   print-string 0, " => "
1451 #?   print-int32-decimal 0, right-col
1452 #?   print-string 0, "\n"
1453   return right-col
1454 }
1455 
1456 fn clear-canvas _env: (addr environment) {
1457   var env/esi: (addr environment) <- copy _env
1458   var screen-ah/edi: (addr handle screen) <- get env, screen
1459   var _screen/eax: (addr screen) <- lookup *screen-ah
1460   var screen/edi: (addr screen) <- copy _screen
1461   clear-screen screen
1462   var nrows/eax: (addr int) <- get env, nrows
1463   var _repl-col/ecx: (addr int) <- get env, code-separator-col
1464   var repl-col/ecx: int <- copy *_repl-col
1465   draw-vertical-line screen, 1, *nrows, repl-col
1466   # wordstar-style cheatsheet of shortcuts
1467   move-cursor screen, *nrows, 0
1468   start-reverse-video screen
1469   print-string screen, " ctrl-q "
1470   reset-formatting screen
1471   print-string screen, " quit "
1472   var menu-start/ebx: int <- copy repl-col
1473   menu-start <- subtract 0x48  # 72 = half the size of the menu
1474   move-cursor screen, *nrows, menu-start
1475   start-reverse-video screen
1476   print-string screen, " ctrl-a "
1477   reset-formatting screen
1478   print-string screen, " ⏮   "
1479   start-reverse-video screen
1480   print-string screen, " ctrl-b "
1481   reset-formatting screen
1482   print-string screen, " ◀ word  "
1483   start-reverse-video screen
1484   print-string screen, " ctrl-f "
1485   reset-formatting screen
1486   print-string screen, " word ▶  "
1487   start-reverse-video screen
1488   print-string screen, " ctrl-e "
1489   reset-formatting screen
1490   print-string screen, " ⏭         "
1491   start-reverse-video screen
1492   print-string screen, " ctrl-l "
1493   reset-formatting screen
1494   print-string screen, " new line "
1495   start-reverse-video screen
1496   print-string screen, " ctrl-u "
1497   reset-formatting screen
1498   print-string screen, " clear  "
1499   start-reverse-video screen
1500   print-string screen, " ctrl-n "
1501   reset-formatting screen
1502   print-string screen, " name value  "
1503   start-reverse-video screen
1504   print-string screen, " ctrl-d "
1505   reset-formatting screen
1506   print-string screen, " define function  "
1507   # primitives
1508   var dummy/eax: int <- render-primitives screen, *nrows, repl-col
1509   # currently defined functions
1510   render-functions screen, repl-col, env
1511 }
1512 
1513 # return value: top-most row written to
1514 fn render-primitives screen: (addr screen), bottom-margin-row: int, right-col: int -> _/eax: int {
1515   # render primitives from the bottom of the screen upward
1516   var row/ecx: int <- copy bottom-margin-row
1517   row <- subtract 1
1518   var col/edx: int <- copy 1
1519   move-cursor screen, row, col
1520   row, col <- render-primitive-group screen, row, col, right-col, "numbers: ", "+ - * / sqrt  "
1521   row, col <- render-primitive-group screen, row, col, right-col, "arrays: ", "len  "
1522   row, col <- render-primitive-group screen, row, col, right-col, "files: ", "open read slurp lines  "
1523   row, col <- render-primitive-group screen, row, col, right-col, "misc: ", "dup swap  "  # hack: keep these at the right of the bottom row
1524   row, col <- render-primitive-group screen, row, col, right-col, "screens: ", "fake-screen print move up down left right  "
1525   # finally print heading up top
1526   row <- decrement
1527   move-cursor screen, row, 1
1528   start-bold screen
1529   print-string screen, "primitives:"
1530   reset-formatting screen
1531   return row
1532 }
1533 
1534 # start at row, col and print the given strings
1535 # move up one row if there isn't enough room before right-col
1536 # return row, col printed until
1537 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 {
1538   var row/ecx: int <- copy _row
1539   var col/edx: int <- copy _col
1540   # decrement row if necessary
1541   var new-col/ebx: int <- copy col
1542   var heading/esi: (addr array byte) <- copy _heading
1543   var len1/eax: int <- length heading
1544   new-col <- add len1
1545   var contents/edi: (addr array byte) <- copy _contents
1546   var len2/eax: int <- length contents
1547   new-col <- add len2
1548   var bound/eax: int <- copy right-col
1549   bound <- decrement
1550   {
1551     compare new-col, bound
1552     break-if-<=
1553     row <- decrement
1554     col <- copy 1
1555   }
1556   move-cursor screen, row, col
1557   start-color screen, 0xf6, 7
1558   print-string screen, heading
1559   reset-formatting screen
1560   print-string screen, contents
1561   return row, new-col
1562 }
1563 
1564 fn render-functions screen: (addr screen), right-col: int, _env: (addr environment) {
1565   var row/ecx: int <- copy 1
1566   var dummy-col/edx: int <- copy right-col
1567   var env/esi: (addr environment) <- copy _env
1568   var functions/esi: (addr handle function) <- get env, functions
1569   {
1570     var curr/eax: (addr function) <- lookup *functions
1571     compare curr, 0
1572     break-if-=
1573     row, dummy-col <- render-function-right-aligned screen, row, right-col, curr
1574     functions <- get curr, next
1575     row <- add 1  # inter-function-margin
1576     loop
1577   }
1578 }
1579 
1580 # print function starting at row, right-aligned before right-col
1581 # return row, col printed until
1582 fn render-function-right-aligned screen: (addr screen), row: int, right-col: int, f: (addr function) -> _/ecx: int, _/edx: int {
1583   var col/edx: int <- copy right-col
1584   col <- subtract 1  # function-right-margin
1585   var col2/ebx: int <- copy col
1586   var width/eax: int <- function-width f
1587   col <- subtract width
1588   var new-row/ecx: int <- copy row
1589   var height/eax: int <- function-height f
1590   new-row <- add height
1591   new-row <- decrement
1592   col <- subtract 1  # function-left-padding
1593   start-color screen, 0, 0xf7
1594   clear-rect screen, row, col, new-row, col2
1595   col <- add 1
1596 #?   var dummy/eax: grapheme <- read-key-from-real-keyboard
1597   render-function screen, row, col, f
1598   new-row <- add 1  # function-bottom-margin
1599   col <- subtract 1  # function-left-padding
1600   col <- subtract 1  # function-left-margin
1601   reset-formatting screen
1602   return new-row, col
1603 }
1604 
1605 # render function starting at row, col
1606 # only single-line functions supported for now
1607 fn render-function screen: (addr screen), row: int, col: int, _f: (addr function) {
1608   var f/esi: (addr function) <- copy _f
1609   var args/ecx: (addr handle word) <- get f, args
1610   move-cursor screen, row, col
1611   print-words-in-reverse screen, args
1612   var name-ah/eax: (addr handle array byte) <- get f, name
1613   var name/eax: (addr array byte) <- lookup *name-ah
1614   start-bold screen
1615   print-string screen, name
1616   reset-formatting screen
1617   start-color screen, 0, 0xf7
1618   increment row
1619   add-to col, 2
1620   move-cursor screen, row, col
1621   print-string screen, "≡ "
1622   var body-ah/eax: (addr handle line) <- get f, body
1623   var body/eax: (addr line) <- lookup *body-ah
1624   var body-words-ah/eax: (addr handle word) <- get body, data
1625   print-words screen, body-words-ah
1626 }
1627 
1628 fn real-grapheme? g: grapheme -> _/eax: boolean {
1629   # if g == newline return true
1630   compare g, 0xa
1631   {
1632     break-if-!=
1633     return 1  # true
1634   }
1635   # if g == tab return true
1636   compare g, 9
1637   {
1638     break-if-!=
1639     return 1  # true
1640   }
1641   # if g < 32 return false
1642   compare g, 0x20
1643   {
1644     break-if->=
1645     return 0  # false
1646   }
1647   # if g <= 255 return true
1648   compare g, 0xff
1649   {
1650     break-if->
1651     return 1  # true
1652   }
1653   # if (g&0xff == Esc) it's an escape sequence
1654   and-with g, 0xff
1655   compare g, 0x1b  # Esc
1656   {
1657     break-if-!=
1658     return 0  # false
1659   }
1660   # otherwise return true
1661   return 1  # true
1662 }