diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2021-03-03 22:09:50 -0800 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2021-03-03 22:21:03 -0800 |
commit | 71e4f3812982dba2efb471283d310224e8db363e (patch) | |
tree | ea111a1acb8b8845dbda39c0e1b4bac1d198143b /linux/tile/environment.mu | |
parent | c6b928be29ac8cdb4e4d6e1eaa20420ff03e5a4c (diff) | |
download | mu-71e4f3812982dba2efb471283d310224e8db363e.tar.gz |
7842 - new directory organization
Baremetal is now the default build target and therefore has its sources at the top-level. Baremetal programs build using the phase-2 Mu toolchain that requires a Linux kernel. This phase-2 codebase which used to be at the top-level is now under the linux/ directory. Finally, the phase-2 toolchain, while self-hosting, has a way to bootstrap from a C implementation, which is now stored in linux/bootstrap. The bootstrap C implementation uses some literate programming tools that are now in linux/bootstrap/tools. So the whole thing has gotten inverted. Each directory should build one artifact and include the main sources (along with standard library). Tools used for building it are relegated to sub-directories, even though those tools are often useful in their own right, and have had lots of interesting programs written using them. A couple of things have gotten dropped in this process: - I had old ways to run on just a Linux kernel, or with a Soso kernel. No more. - I had some old tooling for running a single test at the cursor. I haven't used that lately. Maybe I'll bring it back one day. The reorg isn't done yet. Still to do: - redo documentation everywhere. All the README files, all other markdown, particularly vocabulary.md. - clean up how-to-run comments at the start of programs everywhere - rethink what to do with the html/ directory. Do we even want to keep supporting it? In spite of these shortcomings, all the scripts at the top-level, linux/ and linux/bootstrap are working. The names of the scripts also feel reasonable. This is a good milestone to take stock at.
Diffstat (limited to 'linux/tile/environment.mu')
-rw-r--r-- | linux/tile/environment.mu | 2181 |
1 files changed, 2181 insertions, 0 deletions
diff --git a/linux/tile/environment.mu b/linux/tile/environment.mu new file mode 100644 index 00000000..de771dee --- /dev/null +++ b/linux/tile/environment.mu @@ -0,0 +1,2181 @@ +# The architecture that seems to be crystallizing: the environment has two +# areas: functions and sandbox. +# +# Rendering the environment requires rendering all areas. +# Displaying the cursor requires displaying cursor for the area controlling the cursor. +# Processing events for the environment requires processing events for the area controlling the cursor. +# +# Areas can have dialogs. +# There can also be global dialogs (currently just one: goto function). +# Areas are responsible for rendering their dialogs. +# In practice this results in dialogs encapsulating the state they need to +# decide whether to render. +# +# This will be useful if we add more areas in the future. + +type environment { + screen: (handle screen) + functions: (handle function) + sandboxes: (handle sandbox) + partial-function-name: (handle word) + # at most one of these will be set + cursor-function: (handle function) + cursor-sandbox: (handle sandbox) + # + nrows: int + ncols: int + code-separator-col: int +} + +fn initialize-environment _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + # initialize some predefined function definitions + var functions/eax: (addr handle function) <- get env, functions + create-primitive-functions functions + # initialize first sandbox + var sandbox-ah/ecx: (addr handle sandbox) <- get env, sandboxes + allocate sandbox-ah + var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah + initialize-sandbox sandbox + # initialize cursor sandbox + var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox + copy-object sandbox-ah, cursor-sandbox-ah + # initialize screen + var screen-ah/eax: (addr handle screen) <- get env, screen + var _screen/eax: (addr screen) <- lookup *screen-ah + var screen/edi: (addr screen) <- copy _screen + var nrows/eax: int <- copy 0 + var ncols/ecx: int <- copy 0 + nrows, ncols <- screen-size screen + var dest/edx: (addr int) <- get env, nrows + copy-to *dest, nrows + dest <- get env, ncols + copy-to *dest, ncols + var repl-col/ecx: int <- copy ncols + repl-col <- shift-right 1 + dest <- get env, code-separator-col + copy-to *dest, repl-col +} + +fn initialize-environment-with-fake-screen _self: (addr environment), nrows: int, ncols: int { + var self/esi: (addr environment) <- copy _self + var screen-ah/eax: (addr handle screen) <- get self, screen + allocate screen-ah + var screen-addr/eax: (addr screen) <- lookup *screen-ah + initialize-screen screen-addr, nrows, ncols + initialize-environment self +} + +############# +# Iterate +############# + +fn process _self: (addr environment), key: grapheme { + var self/esi: (addr environment) <- copy _self + var fn-name-ah/eax: (addr handle word) <- get self, partial-function-name + var fn-name/eax: (addr word) <- lookup *fn-name-ah + compare fn-name, 0 + { + break-if-= +#? print-string 0, "processing goto fn\n" + process-goto-dialog self, key + return + } + var function-ah/eax: (addr handle function) <- get self, cursor-function + var function/eax: (addr function) <- lookup *function-ah + compare function, 0 + { + break-if-= +#? print-string 0, "processing function\n" + process-function self, function, key + return + } + var sandbox-ah/eax: (addr handle sandbox) <- get self, cursor-sandbox + var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah + compare sandbox, 0 + { + break-if-= +#? print-string 0, "processing sandbox\n" + process-sandbox self, sandbox, key + return + } +} + +# collect new name in partial-function-name, and move the cursor to function with that name +fn process-goto-dialog _self: (addr environment), key: grapheme { + var self/esi: (addr environment) <- copy _self + var fn-name-ah/edi: (addr handle word) <- get self, partial-function-name + # if 'esc' pressed, cancel goto + compare key, 0x1b/esc + $process-goto-dialog:cancel: { + break-if-!= + clear-object fn-name-ah + return + } + # if 'enter' pressed, location function and set cursor to it + compare key, 0xa/enter + $process-goto-dialog:commit: { + break-if-!= +#? print-string 0, "jump\n" + var fn-name/eax: (addr word) <- lookup *fn-name-ah + var functions/ecx: (addr handle function) <- get self, functions + var dest/edx: (addr handle function) <- get self, cursor-function + callee functions, fn-name, dest + # we won't clear cursor-sandbox until we start supporting multiple sandboxes + clear-object fn-name-ah + # there shouldn't be any need to clear state for other dialogs in the sandbox + return + } + # + compare key, 0x7f/del # backspace on Macs + $process-goto-dialog:backspace: { + break-if-!= + # if not at start, delete grapheme before cursor + var fn-name/eax: (addr word) <- lookup *fn-name-ah + var at-start?/eax: boolean <- cursor-at-start? fn-name + compare at-start?, 0/false + { + break-if-!= + var fn-name/eax: (addr word) <- lookup *fn-name-ah + delete-before-cursor fn-name + } + return + } + # otherwise insert key within current word + var print?/eax: boolean <- real-grapheme? key + $process-goto-dialog:real-grapheme: { + compare print?, 0/false + break-if-= + var fn-name/eax: (addr word) <- lookup *fn-name-ah + add-grapheme-to-word fn-name, key + return + } + # silently ignore other hotkeys +} + +fn process-function _self: (addr environment), _function: (addr function), key: grapheme { + var self/esi: (addr environment) <- copy _self + var function/edi: (addr function) <- copy _function + process-function-edit self, function, key +} + +fn process-function-edit _self: (addr environment), _function: (addr function), key: grapheme { + var self/esi: (addr environment) <- copy _self + var function/edi: (addr function) <- copy _function + var cursor-word-ah/ebx: (addr handle word) <- get function, cursor-word + var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah + var cursor-word/ecx: (addr word) <- copy _cursor-word + compare key, 0x445b1b/left-arrow + $process-function-edit:key-left-arrow: { + break-if-!= +#? print-string 0, "left-arrow\n" + # if not at start, move left within current word + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + { + break-if-!= +#? print-string 0, "cursor left within word\n" + cursor-left cursor-word + return + } + # otherwise move to end of previous word + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= +#? print-string 0, "move to previous word\n" + cursor-to-end prev-word + copy-object prev-word-ah, cursor-word-ah + } + return + } + compare key, 0x435b1b/right-arrow + $process-function-edit:key-right-arrow: { + break-if-!= + # if not at end, move right within current word + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + { + break-if-!= + cursor-right cursor-word + return + } + # otherwise, move to the next word + var next-word-ah/edx: (addr handle word) <- get cursor-word, next + var next-word/eax: (addr word) <- lookup *next-word-ah + { + compare next-word, 0 + break-if-= + cursor-to-start next-word + copy-object next-word-ah, cursor-word-ah + } + return + } + # word-based motions + compare key, 2/ctrl-b + $process-function-edit:prev-word: { + break-if-!= + # jump to previous word if possible + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= + cursor-to-end prev-word + copy-object prev-word-ah, cursor-word-ah + } + return + } + compare key, 6/ctrl-f + $process-function-edit:next-word: { + break-if-!= + # jump to previous word if possible + var next-word-ah/edx: (addr handle word) <- get cursor-word, next + var next-word/eax: (addr word) <- lookup *next-word-ah + { + compare next-word, 0 + break-if-= + cursor-to-end next-word + copy-object next-word-ah, cursor-word-ah + } + return + } + # line-based motions + compare key, 1/ctrl-a + $process-function-edit:start-of-line: { + break-if-!= + # move cursor to start of first word + var body-ah/eax: (addr handle line) <- get function, body + var body/eax: (addr line) <- lookup *body-ah + var body-contents-ah/ecx: (addr handle word) <- get body, data + copy-object body-contents-ah, cursor-word-ah + var body-contents/eax: (addr word) <- lookup *body-contents-ah + cursor-to-start body-contents + return + } + compare key, 5/ctrl-e + $process-function-edit:end-of-line: { + break-if-!= + # move cursor to end of final word + var body-ah/eax: (addr handle line) <- get function, body + var body/eax: (addr line) <- lookup *body-ah + var body-contents-ah/ecx: (addr handle word) <- get body, data + copy-object body-contents-ah, cursor-word-ah + final-word cursor-word-ah, cursor-word-ah + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-end cursor-word + return + } + # bounce to another function + compare key, 7/ctrl-g + $process-function-edit:goto-function: { + break-if-!= + # initialize dialog to name function to jump to + var partial-function-name-ah/eax: (addr handle word) <- get self, partial-function-name + allocate partial-function-name-ah + var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah + initialize-word partial-function-name + return + } + # bounce to sandbox + compare key, 9/tab + $process-function-edit:goto-sandbox: { + break-if-!= + var function-ah/eax: (addr handle function) <- get self, cursor-function + clear-object function-ah + return + } + # editing the current function + compare key, 0x7f/del # backspace on Macs + $process-function-edit:backspace: { + break-if-!= + # if not at start of some word, delete grapheme before cursor within current word + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + { + break-if-!= + delete-before-cursor cursor-word + return + } + # otherwise delete current word and move to end of prev word + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= + cursor-to-end prev-word + delete-next prev-word + copy-object prev-word-ah, cursor-word-ah + } + return + } + compare key, 0x20/space + $process-function-edit:space: { + break-if-!= +#? print-string 0, "space\n" + # if cursor is at start of word, insert word before + { + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + break-if-= + var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev + append-word prev-word-ah + var new-prev-word-ah/eax: (addr handle word) <- get cursor-word, prev + copy-object new-prev-word-ah, cursor-word-ah + return + } + # if start of word is quote and grapheme before cursor is not, just insert it as usual + # TODO: support string escaping + { + var first-grapheme/eax: grapheme <- first-grapheme cursor-word + compare first-grapheme, 0x22/double-quote + break-if-!= + var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word + compare final-grapheme, 0x22/double-quote + break-if-= + break $process-function-edit:space + } + # if start of word is '[' and grapheme before cursor is not ']', just insert it as usual + # TODO: support nested arrays + { + var first-grapheme/eax: grapheme <- first-grapheme cursor-word + compare first-grapheme, 0x5b/[ + break-if-!= + var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word + compare final-grapheme, 0x5d/] + break-if-= + break $process-function-edit:space + } + # otherwise insert word after and move cursor to it for the next key + # (but we'll continue to track the current cursor-word for the rest of this function) + append-word cursor-word-ah + var next-word-ah/eax: (addr handle word) <- get cursor-word, next + copy-object next-word-ah, cursor-word-ah + # if cursor is at end of word, that's all + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + { + break-if-= + return + } + # otherwise we're in the middle of a word + # move everything after cursor to the (just created) next word + var next-word-ah/eax: (addr handle word) <- get cursor-word, next + var _next-word/eax: (addr word) <- lookup *next-word-ah + var next-word/ebx: (addr word) <- copy _next-word + { + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + break-if-!= + var g/eax: grapheme <- pop-after-cursor cursor-word + add-grapheme-to-word next-word, g + loop + } + cursor-to-start next-word + return + } + # otherwise insert key within current word + var g/edx: grapheme <- copy key + var print?/eax: boolean <- real-grapheme? key + $process-function-edit:real-grapheme: { + compare print?, 0/false + break-if-= + add-grapheme-to-word cursor-word, g + return + } + # silently ignore other hotkeys +} + +fn process-sandbox _self: (addr environment), _sandbox: (addr sandbox), key: grapheme { + var self/esi: (addr environment) <- copy _self + var sandbox/edi: (addr sandbox) <- copy _sandbox + var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word + var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah? + compare rename-word-mode?, 0 + { + break-if-= +#? print-string 0, "processing sandbox rename\n" + process-sandbox-rename sandbox, key + return + } + var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function + var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah? + compare define-function-mode?, 0 + { + break-if-= +#? print-string 0, "processing function definition\n" + var functions/ecx: (addr handle function) <- get self, functions + process-sandbox-define sandbox, functions, key + return + } +#? print-string 0, "processing sandbox edit\n" + process-sandbox-edit self, sandbox, key +} + +fn process-sandbox-edit _self: (addr environment), _sandbox: (addr sandbox), key: grapheme { + var self/esi: (addr environment) <- copy _self + var sandbox/edi: (addr sandbox) <- copy _sandbox + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/ebx: (addr handle word) <- get cursor-call-path, word + var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah + var cursor-word/ecx: (addr word) <- copy _cursor-word + compare key, 0x445b1b/left-arrow + $process-sandbox-edit:key-left-arrow: { + break-if-!= +#? print-string 0, "left-arrow\n" + # if not at start, move left within current word + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + { + break-if-!= +#? print-string 0, "cursor left within word\n" + cursor-left cursor-word + return + } + # if current word is expanded, move to the rightmost word in its body + { + var cursor-call-path/esi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var expanded-words/edx: (addr handle call-path) <- get sandbox, expanded-words + var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path + compare curr-word-is-expanded?, 0/false + break-if-= + # update cursor-call-path +#? print-string 0, "curr word is expanded\n" + var self/ecx: (addr environment) <- copy _self + var functions/ecx: (addr handle function) <- get self, functions + var body: (handle line) + var body-ah/eax: (addr handle line) <- address body + function-body functions, cursor-word-ah, body-ah + var body-addr/eax: (addr line) <- lookup *body-ah + var first-word-ah/edx: (addr handle word) <- get body-addr, data + var final-word-h: (handle word) + var final-word-ah/eax: (addr handle word) <- address final-word-h + final-word first-word-ah, final-word-ah + push-to-call-path-element cursor-call-path, final-word-ah + # move cursor to end of word + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-end cursor-word + return + } + # if at first word, look for a caller to jump to + $process-sandbox-edit:key-left-arrow-first-word: { + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + compare prev-word, 0 + break-if-!= + $process-sandbox-edit:key-left-arrow-first-word-and-caller: { +#? print-string 0, "return\n" + { + var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var next-cursor-element-ah/edx: (addr handle call-path-element) <- get cursor-call-path, next + var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah + compare next-cursor-element, 0 + break-if-= $process-sandbox-edit:key-left-arrow-first-word-and-caller + copy-object next-cursor-element-ah, cursor-call-path-ah + } + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-word <- copy _cursor-word + } + } + # then move to end of previous word + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= +#? print-string 0, "move to previous word\n" + cursor-to-end prev-word +#? { +#? var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path +#? var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah +#? var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word +#? var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah +#? var cursor-word/ebx: (addr word) <- copy _cursor-word +#? print-string 0, "word at cursor before: " +#? print-word 0, cursor-word +#? print-string 0, "\n" +#? } + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + decrement-final-element cursor-call-path +#? { +#? var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path +#? var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah +#? var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word +#? var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah +#? var cursor-word/ebx: (addr word) <- copy _cursor-word +#? print-string 0, "word at cursor after: " +#? print-word 0, cursor-word +#? print-string 0, "\n" +#? } + } + return + } + compare key, 0x435b1b/right-arrow + $process-sandbox-edit:key-right-arrow: { + break-if-!= + # if not at end, move right within current word + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + { + break-if-!= +#? print-string 0, "a\n" + cursor-right cursor-word + return + } + # if at final word, look for a caller to jump to + { + var next-word-ah/edx: (addr handle word) <- get cursor-word, next + var next-word/eax: (addr word) <- lookup *next-word-ah + compare next-word, 0 + break-if-!= + var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var next-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next + var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah + compare next-cursor-element, 0 + break-if-= + copy-object next-cursor-element-ah, cursor-call-path-ah + return + } + # otherwise, move to the next word + var next-word-ah/edx: (addr handle word) <- get cursor-word, next + var next-word/eax: (addr word) <- lookup *next-word-ah + { + compare next-word, 0 + break-if-= +#? print-string 0, "b\n" + cursor-to-start next-word + # . . cursor-word now out of date + var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path + increment-final-element cursor-call-path + # Is the new cursor word expanded? If so, it's a function call. Add a + # new level to the cursor-call-path for the call's body. + $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded: { +#? print-string 0, "c\n" + { + var expanded-words/eax: (addr handle call-path) <- get sandbox, expanded-words + var curr-word-is-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path + compare curr-word-is-expanded?, 0/false + break-if-= $process-sandbox-edit:key-right-arrow-next-word-is-call-expanded + } + var callee-h: (handle function) + var callee-ah/edx: (addr handle function) <- address callee-h + var functions/ebx: (addr handle function) <- get self, functions + callee functions, next-word, callee-ah + var callee/eax: (addr function) <- lookup *callee-ah + var callee-body-ah/eax: (addr handle line) <- get callee, body + var callee-body/eax: (addr line) <- lookup *callee-body-ah + var callee-body-first-word/edx: (addr handle word) <- get callee-body, data + push-to-call-path-element cursor-call-path, callee-body-first-word + # position cursor at left + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-start cursor-word +#? print-string 0, "d\n" + return + } + } + return + } + compare key, 0xa/enter + { + break-if-!= + # toggle display of subsidiary stack + toggle-cursor-word sandbox + return + } + compare key, 0xc/ctrl-l + $process-sandbox-edit:new-line: { + break-if-!= + # new line in sandbox + append-line sandbox + return + } + # word-based motions + compare key, 2/ctrl-b + $process-sandbox-edit:prev-word: { + break-if-!= + # jump to previous word at same level + var prev-word-ah/edx: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= + cursor-to-end prev-word + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + decrement-final-element cursor-call-path + return + } + # if previous word doesn't exist, try to bump up one level + { + var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next + var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah + compare caller-cursor-element, 0 + break-if-= + # check if previous word exists in caller + var caller-word-ah/eax: (addr handle word) <- get caller-cursor-element, word + var caller-word/eax: (addr word) <- lookup *caller-word-ah + var word-before-caller-ah/eax: (addr handle word) <- get caller-word, prev + var word-before-caller/eax: (addr word) <- lookup *word-before-caller-ah + compare word-before-caller, 0 + break-if-= + # if so jump to it + drop-from-call-path-element cursor-call-path-ah + decrement-final-element cursor-call-path-ah + return + } + } + compare key, 6/ctrl-f + $process-sandbox-edit:next-word: { + break-if-!= +#? print-string 0, "AA\n" + # jump to previous word at same level + var next-word-ah/edx: (addr handle word) <- get cursor-word, next + var next-word/eax: (addr word) <- lookup *next-word-ah + { + compare next-word, 0 + break-if-= +#? print-string 0, "BB\n" + cursor-to-end next-word + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + increment-final-element cursor-call-path + return + } + # if next word doesn't exist, try to bump up one level +#? print-string 0, "CC\n" + var cursor-call-path-ah/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var caller-cursor-element-ah/ecx: (addr handle call-path-element) <- get cursor-call-path, next + var caller-cursor-element/eax: (addr call-path-element) <- lookup *caller-cursor-element-ah + compare caller-cursor-element, 0 + break-if-= +#? print-string 0, "DD\n" + copy-object caller-cursor-element-ah, cursor-call-path-ah + return + } + compare key, 7/ctrl-g + $process-sandbox-edit:goto-function: { + break-if-!= + # initialize dialog to name function to jump to + var partial-function-name-ah/eax: (addr handle word) <- get self, partial-function-name + allocate partial-function-name-ah + var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah + initialize-word partial-function-name + return + } + # line-based motions + compare key, 1/ctrl-a + $process-sandbox-edit:start-of-line: { + break-if-!= + # move cursor up past all calls and to start of line + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + drop-nested-calls cursor-call-path-ah + move-final-element-to-start-of-line cursor-call-path-ah + # move cursor to start of word + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-start cursor-word + # this works as long as the first word isn't expanded + # but we don't expect to see zero-arg functions first-up + return + } + compare key, 5/ctrl-e + $process-sandbox-edit:end-of-line: { + break-if-!= + # move cursor up past all calls and to start of line + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + drop-nested-calls cursor-call-path-ah + move-final-element-to-end-of-line cursor-call-path-ah + # move cursor to end of word + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-end cursor-word + # this works because expanded words lie to the right of their bodies + # so the final word is always guaranteed to be at the top-level + return + } + compare key, 0x15/ctrl-u + $process-sandbox-edit:clear-line: { + break-if-!= + # clear line in sandbox + initialize-sandbox sandbox + return + } + # if cursor is within a call, disable editing hotkeys below + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var next-cursor-element-ah/eax: (addr handle call-path-element) <- get cursor-call-path, next + var next-cursor-element/eax: (addr call-path-element) <- lookup *next-cursor-element-ah + compare next-cursor-element, 0 + { + break-if-= + return + } + # - remaining keys only work at the top row outside any function calls + compare key, 0x7f/del # backspace on Macs + $process-sandbox-edit:backspace: { + break-if-!= + # if not at start of some word, delete grapheme before cursor within current word + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + { + break-if-!= + delete-before-cursor cursor-word + return + } + # otherwise delete current word and move to end of prev word + var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + { + compare prev-word, 0 + break-if-= + cursor-to-end prev-word + delete-next prev-word + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + decrement-final-element cursor-call-path + } + return + } + compare key, 0x20/space + $process-sandbox-edit:space: { + break-if-!= +#? print-string 0, "space\n" + # if cursor is at start of word, insert word before + { + var at-start?/eax: boolean <- cursor-at-start? cursor-word + compare at-start?, 0/false + break-if-= + var prev-word-ah/eax: (addr handle word) <- get cursor-word, prev + append-word prev-word-ah + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + decrement-final-element cursor-call-path + return + } + # if start of word is quote and grapheme before cursor is not, just insert it as usual + # TODO: support string escaping + { + var first-grapheme/eax: grapheme <- first-grapheme cursor-word + compare first-grapheme, 0x22/double-quote + break-if-!= + var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word + compare final-grapheme, 0x22/double-quote + break-if-= + break $process-sandbox-edit:space + } + # if start of word is '[' and grapheme before cursor is not ']', just insert it as usual + # TODO: support nested arrays + { + var first-grapheme/eax: grapheme <- first-grapheme cursor-word + compare first-grapheme, 0x5b/[ + break-if-!= + var final-grapheme/eax: grapheme <- grapheme-before-cursor cursor-word + compare final-grapheme, 0x5d/] + break-if-= + break $process-sandbox-edit:space + } + # otherwise insert word after and move cursor to it for the next key + # (but we'll continue to track the current cursor-word for the rest of this function) + append-word cursor-word-ah + var cursor-call-path/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + increment-final-element cursor-call-path + # if cursor is at end of word, that's all + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + { + break-if-= + return + } + # otherwise we're in the middle of a word + # move everything after cursor to the (just created) next word + var next-word-ah/eax: (addr handle word) <- get cursor-word, next + var _next-word/eax: (addr word) <- lookup *next-word-ah + var next-word/ebx: (addr word) <- copy _next-word + { + var at-end?/eax: boolean <- cursor-at-end? cursor-word + compare at-end?, 0/false + break-if-!= + var g/eax: grapheme <- pop-after-cursor cursor-word + add-grapheme-to-word next-word, g + loop + } + cursor-to-start next-word + return + } + compare key, 0xe/ctrl-n + $process:rename-word: { + break-if-!= + # TODO: ensure current word is not a function + # rename word at cursor + var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-cursor-word + allocate new-name-ah + var new-name/eax: (addr word) <- lookup *new-name-ah + initialize-word new-name + return + } + compare key, 4/ctrl-d + $process:define-function: { + break-if-!= + # define function out of line at cursor + var new-name-ah/eax: (addr handle word) <- get sandbox, partial-name-for-function + allocate new-name-ah + var new-name/eax: (addr word) <- lookup *new-name-ah + initialize-word new-name + return + } + # otherwise insert key within current word + var g/edx: grapheme <- copy key + var print?/eax: boolean <- real-grapheme? key + $process-sandbox-edit:real-grapheme: { + compare print?, 0/false + break-if-= + add-grapheme-to-word cursor-word, g + return + } + # silently ignore other hotkeys +} + +# collect new name in partial-name-for-cursor-word, and then rename the word +# at cursor to it +# Precondition: cursor-call-path is a singleton (not within a call) +fn process-sandbox-rename _sandbox: (addr sandbox), key: grapheme { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-cursor-word + # if 'esc' pressed, cancel rename + compare key, 0x1b/esc + $process-sandbox-rename:cancel: { + break-if-!= + clear-object new-name-ah + return + } + # if 'enter' pressed, perform rename + compare key, 0xa/enter + $process-sandbox-rename:commit: { + break-if-!= +#? print-string 0, "rename\n" + # new line + var new-line-h: (handle line) + var new-line-ah/eax: (addr handle line) <- address new-line-h + allocate new-line-ah + var new-line/eax: (addr line) <- lookup *new-line-ah + initialize-line new-line + var new-line-word-ah/ecx: (addr handle word) <- get new-line, data + { + # move word at cursor to new line + var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor/eax: (addr call-path-element) <- lookup *cursor-ah + var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word +#? print-string 0, "cursor before at word " +#? { +#? var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah +#? print-word 0, cursor-word +#? print-string 0, "\n" +#? } + move-word-contents word-at-cursor-ah, new-line-word-ah + # copy name to word at cursor + copy-word-contents-before-cursor new-name-ah, word-at-cursor-ah +#? print-string 0, "cursor after at word " +#? { +#? var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah +#? print-word 0, cursor-word +#? print-string 0, "\n" +#? var foo/eax: int <- copy cursor-word +#? print-int32-hex 0, foo +#? print-string 0, "\n" +#? } +#? print-string 0, "new name word " +#? { +#? var new-name/eax: (addr word) <- lookup *new-name-ah +#? print-word 0, new-name +#? print-string 0, "\n" +#? var foo/eax: int <- copy new-name +#? print-int32-hex 0, foo +#? print-string 0, "\n" +#? } + } + # prepend '=' to name + { + var new-name/eax: (addr word) <- lookup *new-name-ah + cursor-to-start new-name + add-grapheme-to-word new-name, 0x3d/= + } + # append name to new line + chain-words new-line-word-ah, new-name-ah + # new-line->next = sandbox->data + var new-line-next/ecx: (addr handle line) <- get new-line, next + var sandbox-slot/edx: (addr handle line) <- get sandbox, data + copy-object sandbox-slot, new-line-next + # sandbox->data = new-line + copy-handle new-line-h, sandbox-slot + # clear partial-name-for-cursor-word + clear-object new-name-ah +#? var cursor-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path +#? var cursor/eax: (addr call-path-element) <- lookup *cursor-ah +#? var word-at-cursor-ah/eax: (addr handle word) <- get cursor, word +#? print-string 0, "cursor after rename: " +#? { +#? var cursor-word/eax: (addr word) <- lookup *word-at-cursor-ah +#? print-word 0, cursor-word +#? print-string 0, " -- " +#? var foo/eax: int <- copy cursor-word +#? print-int32-hex 0, foo +#? print-string 0, "\n" +#? } + return + } + # + compare key, 0x7f/del # backspace on Macs + $process-sandbox-rename:backspace: { + break-if-!= + # if not at start, delete grapheme before cursor + var new-name/eax: (addr word) <- lookup *new-name-ah + var at-start?/eax: boolean <- cursor-at-start? new-name + compare at-start?, 0/false + { + break-if-!= + var new-name/eax: (addr word) <- lookup *new-name-ah + delete-before-cursor new-name + } + return + } + # otherwise insert key within current word + var print?/eax: boolean <- real-grapheme? key + $process-sandbox-rename:real-grapheme: { + compare print?, 0/false + break-if-= + var new-name/eax: (addr word) <- lookup *new-name-ah + add-grapheme-to-word new-name, key + return + } + # silently ignore other hotkeys +} + +# collect new name in partial-name-for-function, and then define the last line +# of the sandbox to be a new function with that name. Replace the last line +# with a call to the appropriate function. +# Precondition: cursor-call-path is a singleton (not within a call) +fn process-sandbox-define _sandbox: (addr sandbox), functions: (addr handle function), key: grapheme { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var new-name-ah/edi: (addr handle word) <- get sandbox, partial-name-for-function + # if 'esc' pressed, cancel define + compare key, 0x1b/esc + $process-sandbox-define:cancel: { + break-if-!= + clear-object new-name-ah + return + } + # if 'enter' pressed, perform define + compare key, 0xa/enter + $process-sandbox-define:commit: { + break-if-!= +#? print-string 0, "define\n" + # create new function + var new-function: (handle function) + var new-function-ah/ecx: (addr handle function) <- address new-function + allocate new-function-ah + var _new-function/eax: (addr function) <- lookup *new-function-ah + var new-function/ebx: (addr function) <- copy _new-function + var dest/edx: (addr handle function) <- get new-function, next + copy-object functions, dest + copy-object new-function-ah, functions + # set function name to new-name + var new-name/eax: (addr word) <- lookup *new-name-ah + var dest/edx: (addr handle array byte) <- get new-function, name + word-to-string new-name, dest + # move final line to body + var body-ah/eax: (addr handle line) <- get new-function, body + allocate body-ah + var body/eax: (addr line) <- lookup *body-ah + var body-contents/ecx: (addr handle word) <- get body, data + var final-line-storage: (handle line) + var final-line-ah/eax: (addr handle line) <- address final-line-storage + final-line sandbox, final-line-ah + var final-line/eax: (addr line) <- lookup *final-line-ah + var final-line-contents/eax: (addr handle word) <- get final-line, data + copy-object final-line-contents, body-contents + var cursor-word-ah/ecx: (addr handle word) <- get new-function, cursor-word + copy-object final-line-contents, cursor-word-ah + { + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + cursor-to-start cursor-word + } + # + copy-unbound-words-to-args functions + # + var empty-word: (handle word) + copy-handle empty-word, final-line-contents + construct-call functions, final-line-contents + # clear partial-name-for-function + var empty-word: (handle word) + copy-handle empty-word, new-name-ah + # update cursor + var final-line/eax: (addr line) <- lookup final-line-storage + var cursor-call-path-ah/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path + allocate cursor-call-path-ah # leak + initialize-path-from-line final-line, cursor-call-path-ah + return + } + # + compare key, 0x7f/del # backspace on Macs + $process-sandbox-define:backspace: { + break-if-!= + # if not at start, delete grapheme before cursor + var new-name/eax: (addr word) <- lookup *new-name-ah + var at-start?/eax: boolean <- cursor-at-start? new-name + compare at-start?, 0/false + { + break-if-!= + var new-name/eax: (addr word) <- lookup *new-name-ah + delete-before-cursor new-name + } + return + } + # otherwise insert key within current word + var print?/eax: boolean <- real-grapheme? key + $process-sandbox-define:real-grapheme: { + compare print?, 0/false + break-if-= + var new-name/eax: (addr word) <- lookup *new-name-ah + add-grapheme-to-word new-name, key + return + } + # silently ignore other hotkeys +} + +# extract from the body of the first function in 'functions' all words that +# aren't defined in the rest of 'functions'. Prepend them in reverse order. +# Assumes function body is a single line for now. +fn copy-unbound-words-to-args _functions: (addr handle function) { + # target + var target-ah/eax: (addr handle function) <- copy _functions + var _target/eax: (addr function) <- lookup *target-ah + var target/esi: (addr function) <- copy _target + var dest-ah/edi: (addr handle word) <- get target, args + # next + var functions-ah/edx: (addr handle function) <- get target, next + # src + var line-ah/eax: (addr handle line) <- get target, body + var line/eax: (addr line) <- lookup *line-ah + var curr-ah/eax: (addr handle word) <- get line, data + var curr/eax: (addr word) <- lookup *curr-ah + { + compare curr, 0 + break-if-= + $copy-unbound-words-to-args:loop-iter: { + # is it a number? + { + var is-int?/eax: boolean <- word-is-decimal-integer? curr + compare is-int?, 0/false + break-if-!= $copy-unbound-words-to-args:loop-iter + } + # is it a pre-existing function? + var bound?/ebx: boolean <- bound-function? curr, functions-ah + compare bound?, 0/false + break-if-!= + # is it already bound as an arg? + var dup?/ebx: boolean <- arg-exists? _functions, curr # _functions = target-ah + compare dup?, 0/false + break-if-!= $copy-unbound-words-to-args:loop-iter + # push copy of curr before dest-ah + var rest-h: (handle word) + var rest-ah/ecx: (addr handle word) <- address rest-h + copy-object dest-ah, rest-ah + copy-word curr, dest-ah + chain-words dest-ah, rest-ah + } + var next-ah/ecx: (addr handle word) <- get curr, next + curr <- lookup *next-ah + loop + } +} + +fn bound-function? w: (addr word), functions-ah: (addr handle function) -> _/ebx: boolean { + var result/ebx: boolean <- copy 1/true + { + ## numbers + # if w == "+" return true + var subresult/eax: boolean <- word-equal? w, "+" + compare subresult, 0/false + break-if-!= + # if w == "-" return true + subresult <- word-equal? w, "-" + compare subresult, 0/false + break-if-!= + # if w == "*" return true + subresult <- word-equal? w, "*" + compare subresult, 0/false + break-if-!= + # if w == "/" return true + subresult <- word-equal? w, "/" + compare subresult, 0/false + break-if-!= + # if w == "sqrt" return true + subresult <- word-equal? w, "sqrt" + compare subresult, 0/false + break-if-!= + ## strings/arrays + # if w == "len" return true + subresult <- word-equal? w, "len" + compare subresult, 0/false + break-if-!= + ## files + # if w == "open" return true + subresult <- word-equal? w, "open" + compare subresult, 0/false + break-if-!= + # if w == "read" return true + subresult <- word-equal? w, "read" + compare subresult, 0/false + break-if-!= + # if w == "slurp" return true + subresult <- word-equal? w, "slurp" + compare subresult, 0/false + break-if-!= + # if w == "lines" return true + subresult <- word-equal? w, "lines" + compare subresult, 0/false + break-if-!= + ## screens + # if w == "fake-screen" return true + subresult <- word-equal? w, "fake-screen" + compare subresult, 0/false + break-if-!= + # if w == "print" return true + subresult <- word-equal? w, "print" + compare subresult, 0/false + break-if-!= + # if w == "move" return true + subresult <- word-equal? w, "move" + compare subresult, 0/false + break-if-!= + # if w == "up" return true + subresult <- word-equal? w, "up" + compare subresult, 0/false + break-if-!= + # if w == "down" return true + subresult <- word-equal? w, "down" + compare subresult, 0/false + break-if-!= + # if w == "left" return true + subresult <- word-equal? w, "left" + compare subresult, 0/false + break-if-!= + # if w == "right" return true + subresult <- word-equal? w, "right" + compare subresult, 0/false + break-if-!= + ## hacks + # if w == "dup" return true + subresult <- word-equal? w, "dup" + compare subresult, 0/false + break-if-!= + # if w == "swap" return true + subresult <- word-equal? w, "swap" + compare subresult, 0/false + break-if-!= + # return w in functions + var out-h: (handle function) + var out/eax: (addr handle function) <- address out-h + callee functions-ah, w, out + var found?/eax: (addr function) <- lookup *out + result <- copy found? + } + return result +} + +fn arg-exists? _f-ah: (addr handle function), arg: (addr word) -> _/ebx: boolean { + var f-ah/eax: (addr handle function) <- copy _f-ah + var f/eax: (addr function) <- lookup *f-ah + var args-ah/eax: (addr handle word) <- get f, args + var result/ebx: boolean <- word-exists? args-ah, arg + return result +} + +# construct a call to `f` with copies of exactly its args +fn construct-call _f-ah: (addr handle function), _dest-ah: (addr handle word) { + var f-ah/eax: (addr handle function) <- copy _f-ah + var _f/eax: (addr function) <- lookup *f-ah + var f/esi: (addr function) <- copy _f + # append args in reverse + var args-ah/eax: (addr handle word) <- get f, args + var dest-ah/edi: (addr handle word) <- copy _dest-ah + copy-words-in-reverse args-ah, dest-ah + # append name + var name-ah/eax: (addr handle array byte) <- get f, name + var name/eax: (addr array byte) <- lookup *name-ah + append-word-at-end-with dest-ah, name +} + +fn word-index _words: (addr handle word), _n: int, out: (addr handle word) { + var n/ecx: int <- copy _n + { + compare n, 0 + break-if-!= + copy-object _words, out + return + } + var words-ah/eax: (addr handle word) <- copy _words + var words/eax: (addr word) <- lookup *words-ah + var next/eax: (addr handle word) <- get words, next + n <- decrement + word-index next, n, out +} + +fn toggle-cursor-word _sandbox: (addr sandbox) { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words + var cursor-call-path/ecx: (addr handle call-path-element) <- get sandbox, cursor-call-path +#? print-string 0, "cursor call path: " +#? dump-call-path-element 0, cursor-call-path +#? print-string 0, "expanded words:\n" +#? dump-call-paths 0, expanded-words + var already-expanded?/eax: boolean <- find-in-call-paths expanded-words, cursor-call-path + compare already-expanded?, 0/false + { + break-if-!= +#? print-string 0, "expand\n" + # if not already-expanded, insert + insert-in-call-path expanded-words cursor-call-path +#? print-string 0, "expanded words now:\n" +#? dump-call-paths 0, expanded-words + return + } + { + break-if-= + # otherwise delete + delete-in-call-path expanded-words cursor-call-path + } +} + +fn append-line _sandbox: (addr sandbox) { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var line-ah/ecx: (addr handle line) <- get sandbox, data + { + var line/eax: (addr line) <- lookup *line-ah + var next-line-ah/edx: (addr handle line) <- get line, next + var next-line/eax: (addr line) <- lookup *next-line-ah + compare next-line, 0 + break-if-= + line-ah <- copy next-line-ah + loop + } + var line/eax: (addr line) <- lookup *line-ah + var final-line-ah/edx: (addr handle line) <- get line, next + allocate final-line-ah + var final-line/eax: (addr line) <- lookup *final-line-ah + initialize-line final-line + var final-prev/eax: (addr handle line) <- get final-line, prev + copy-object line-ah, final-prev + # clear cursor + var final-line/eax: (addr line) <- lookup *final-line-ah + var word-ah/ecx: (addr handle word) <- get final-line, data + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var dest/eax: (addr handle word) <- get cursor-call-path, word + copy-object word-ah, dest +} + +############# +# Visualize +############# + +fn evaluate-environment _env: (addr environment), stack: (addr value-stack) { + var env/esi: (addr environment) <- copy _env + # functions + var functions/edx: (addr handle function) <- get env, functions + # line + var sandbox-ah/esi: (addr handle sandbox) <- get env, sandboxes + var sandbox/eax: (addr sandbox) <- lookup *sandbox-ah + var line-ah/eax: (addr handle line) <- get sandbox, data + var _line/eax: (addr line) <- lookup *line-ah + var line/esi: (addr line) <- copy _line + evaluate functions, 0, line, 0, stack +} + +fn render _env: (addr environment) { +#? print-string 0, "== render\n" + var env/esi: (addr environment) <- copy _env + clear-canvas env + # menu + render-menu env + # screen + var screen-ah/eax: (addr handle screen) <- get env, screen + var _screen/eax: (addr screen) <- lookup *screen-ah + var screen/edi: (addr screen) <- copy _screen + # functions + var sep-col/eax: (addr int) <- get env, code-separator-col + var functions/edx: (addr handle function) <- get env, functions + render-functions screen, *sep-col, env + # sandbox + var repl-col/ecx: int <- copy *sep-col + repl-col <- add 2/repl-margin-left + var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox + var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah + # bindings + var bindings-storage: table + var bindings/ebx: (addr table) <- address bindings-storage + initialize-table bindings, 0x10 +#? print-string 0, "render-sandbox {\n" + render-sandbox screen, functions, bindings, cursor-sandbox, 3, repl-col +#? print-string 0, "render-sandbox }\n" + # dialogs + render-goto-dialog screen, env + # + position-cursor screen, env +} + +# draw a wordstar-style cheatsheet of shortcuts on the bottom line of the screen +fn render-menu _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var cursor-function-ah/eax: (addr handle function) <- get env, cursor-function + var cursor-function/eax: (addr function) <- lookup *cursor-function-ah + { + compare cursor-function, 0 + break-if-= + render-function-menu env + return + } + var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox + var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah + { + compare cursor-sandbox, 0 + break-if-= + render-sandbox-menu env + return + } +} + +# HACK: areas currently responsible for positioning their dialogs' cursors. So +# we just do nothing here if a dialog is up. +fn position-cursor screen: (addr screen), _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var goto-function-ah/eax: (addr handle word) <- get env, partial-function-name + var goto-function/eax: (addr word) <- lookup *goto-function-ah + { + compare goto-function, 0 + break-if-= + return + } + var cursor-function-ah/eax: (addr handle function) <- get env, cursor-function + var cursor-function/eax: (addr function) <- lookup *cursor-function-ah + { + compare cursor-function, 0 + break-if-= + var cursor-row/ecx: (addr int) <- get cursor-function, cursor-row + var cursor-col/eax: (addr int) <- get cursor-function, cursor-col + move-cursor screen, *cursor-row, *cursor-col + return + } + var cursor-sandbox-ah/eax: (addr handle sandbox) <- get env, cursor-sandbox + var cursor-sandbox/eax: (addr sandbox) <- lookup *cursor-sandbox-ah + { + compare cursor-sandbox, 0 + break-if-= + # if in a dialog, return + { + var partial-word-rename-ah/eax: (addr handle word) <- get cursor-sandbox, partial-name-for-cursor-word + var partial-word-rename/eax: (addr word) <- lookup *partial-word-rename-ah + compare partial-word-rename, 0 + break-if-= + return + } + { + var partial-function-name-ah/eax: (addr handle word) <- get cursor-sandbox, partial-name-for-function + var partial-function-name/eax: (addr word) <- lookup *partial-function-name-ah + compare partial-function-name, 0 + break-if-= + return + } + var cursor-row/ecx: (addr int) <- get cursor-sandbox, cursor-row + var cursor-col/eax: (addr int) <- get cursor-sandbox, cursor-col + move-cursor screen, *cursor-row, *cursor-col + } +} + +fn render-goto-dialog screen: (addr screen), _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var goto-function-mode-ah?/eax: (addr handle word) <- get env, partial-function-name + var goto-function-mode?/eax: (addr word) <- lookup *goto-function-mode-ah? + compare goto-function-mode?, 0/false + break-if-= + # clear a space for the dialog + var top-row/ebx: int <- copy 3 + var bottom-row/edx: int <- copy 9 + var sep-col/eax: (addr int) <- get env, code-separator-col + var left-col/ecx: int <- copy *sep-col + left-col <- subtract 0x10 + var right-col/eax: int <- copy *sep-col + right-col <- add 0x10 + clear-rect screen, top-row, left-col, bottom-row, right-col + draw-box screen, top-row, left-col, bottom-row, right-col + # render a little menu for the dialog + var menu-row/eax: int <- copy bottom-row + menu-row <- decrement + var menu-col/edx: int <- copy left-col + menu-col <- add 2 + move-cursor screen, menu-row, menu-col + start-reverse-video screen + print-string screen, " esc " + reset-formatting screen + print-string screen, " cancel " + start-reverse-video screen + print-string screen, " enter " + reset-formatting screen + print-string screen, " jump " + # draw the word, positioned appropriately around the cursor + var start-col/ecx: int <- copy left-col + start-col <- increment + move-cursor screen, 6, start-col # cursor-row + var word-ah?/edx: (addr handle word) <- get env, partial-function-name + var word/eax: (addr word) <- lookup *word-ah? + print-word screen, word +} + +fn render-sandbox screen: (addr screen), functions: (addr handle function), bindings: (addr table), _sandbox: (addr sandbox), top-row: int, left-col: int { + var sandbox/esi: (addr sandbox) <- copy _sandbox + # line + var curr-line-ah/eax: (addr handle line) <- get sandbox, data + var _curr-line/eax: (addr line) <- lookup *curr-line-ah + var curr-line/ecx: (addr line) <- copy _curr-line + # + var curr-row/edx: int <- copy top-row + # cursor row, col + var cursor-row-addr: (addr int) + var tmp/eax: (addr int) <- get sandbox, cursor-row + copy-to cursor-row-addr, tmp + var cursor-col-addr: (addr int) + tmp <- get sandbox, cursor-col + copy-to cursor-col-addr, tmp + # render all but final line without stack +#? print-string 0, "render all but final line\n" + { + var next-line-ah/eax: (addr handle line) <- get curr-line, next + var next-line/eax: (addr line) <- lookup *next-line-ah + compare next-line, 0 + break-if-= + { + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var cursor-word/eax: (addr word) <- lookup *cursor-word-ah + # 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 + render-line-without-stack screen, curr-line, curr-row, left-col, cursor-word, cursor-row-addr, cursor-col-addr + } + curr-line <- copy next-line + curr-row <- add 2 + loop + } + # + render-final-line-with-stack screen, functions, bindings, sandbox, curr-row, left-col, cursor-row-addr, cursor-col-addr + # at most one of the following dialogs will be rendered + render-rename-dialog screen, sandbox + render-define-dialog screen, sandbox +} + +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) { + var sandbox/esi: (addr sandbox) <- copy _sandbox + # expanded-words + var expanded-words/edi: (addr handle call-path) <- get sandbox, expanded-words + # cursor-word + var cursor-call-path-ah/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + var cursor-call-path/eax: (addr call-path-element) <- lookup *cursor-call-path-ah + var cursor-word-ah/eax: (addr handle word) <- get cursor-call-path, word + var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah + var cursor-word/ebx: (addr word) <- copy _cursor-word +#? print-string 0, "word at cursor: " +#? print-word 0, cursor-word +#? print-string 0, "\n" + # cursor-call-path + var cursor-call-path: (addr handle call-path-element) + { + var src/eax: (addr handle call-path-element) <- get sandbox, cursor-call-path + copy-to cursor-call-path, src + } + # first line + var first-line-ah/eax: (addr handle line) <- get sandbox, data + var _first-line/eax: (addr line) <- lookup *first-line-ah + var first-line/edx: (addr line) <- copy _first-line + # final line + var final-line-storage: (handle line) + var final-line-ah/eax: (addr handle line) <- address final-line-storage + final-line sandbox, final-line-ah + var final-line/eax: (addr line) <- lookup *final-line-ah + # curr-path + var curr-path-storage: (handle call-path-element) + var curr-path/ecx: (addr handle call-path-element) <- address curr-path-storage + allocate curr-path # leak + initialize-path-from-line final-line, curr-path + # + 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 +} + +fn final-line _sandbox: (addr sandbox), out: (addr handle line) { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var curr-line-ah/ecx: (addr handle line) <- get sandbox, data + { + var curr-line/eax: (addr line) <- lookup *curr-line-ah + var next-line-ah/edx: (addr handle line) <- get curr-line, next + var next-line/eax: (addr line) <- lookup *next-line-ah + compare next-line, 0 + break-if-= + curr-line-ah <- copy next-line-ah + loop + } + copy-object curr-line-ah, out +} + +fn render-rename-dialog screen: (addr screen), _sandbox: (addr sandbox) { + var sandbox/edi: (addr sandbox) <- copy _sandbox + var rename-word-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-cursor-word + var rename-word-mode?/eax: (addr word) <- lookup *rename-word-mode-ah? + compare rename-word-mode?, 0/false + break-if-= + # clear a space for the dialog + var cursor-row/ebx: (addr int) <- get sandbox, cursor-row + var top-row/eax: int <- copy *cursor-row + top-row <- subtract 3 + var bottom-row/ecx: int <- copy *cursor-row + bottom-row <- add 3 + var cursor-col/ebx: (addr int) <- get sandbox, cursor-col + var left-col/edx: int <- copy *cursor-col + left-col <- subtract 0x10 + var right-col/ebx: int <- copy *cursor-col + right-col <- add 0x10 + clear-rect screen, top-row, left-col, bottom-row, right-col + draw-box screen, top-row, left-col, bottom-row, right-col + # render a little menu for the dialog + var menu-row/ecx: int <- copy bottom-row + menu-row <- decrement + var menu-col/edx: int <- copy left-col + menu-col <- add 2 + move-cursor screen, menu-row, menu-col + start-reverse-video screen + print-string screen, " esc " + reset-formatting screen + print-string screen, " cancel " + start-reverse-video screen + print-string screen, " enter " + reset-formatting screen + print-string screen, " rename " + # draw the word, positioned appropriately around the cursor + var cursor-col/ebx: (addr int) <- get sandbox, cursor-col + var start-col/ecx: int <- copy *cursor-col + var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-cursor-word + var word/eax: (addr word) <- lookup *word-ah? + var cursor-index/eax: int <- cursor-index word + start-col <- subtract cursor-index + var cursor-row/ebx: (addr int) <- get sandbox, cursor-row + move-cursor screen, *cursor-row, start-col + var word/eax: (addr word) <- lookup *word-ah? + print-word screen, word +} + +fn render-define-dialog screen: (addr screen), _sandbox: (addr sandbox) { + var sandbox/edi: (addr sandbox) <- copy _sandbox + var define-function-mode-ah?/ecx: (addr handle word) <- get sandbox, partial-name-for-function + var define-function-mode?/eax: (addr word) <- lookup *define-function-mode-ah? + compare define-function-mode?, 0/false + break-if-= + # clear a space for the dialog + var cursor-row/ebx: (addr int) <- get sandbox, cursor-row + var top-row/eax: int <- copy *cursor-row + top-row <- subtract 3 + var bottom-row/ecx: int <- copy *cursor-row + bottom-row <- add 3 + var cursor-col/ebx: (addr int) <- get sandbox, cursor-col + var left-col/edx: int <- copy *cursor-col + left-col <- subtract 0x10 + var right-col/ebx: int <- copy *cursor-col + right-col <- add 0x10 + clear-rect screen, top-row, left-col, bottom-row, right-col + draw-box screen, top-row, left-col, bottom-row, right-col + # render a little menu for the dialog + var menu-row/ecx: int <- copy bottom-row + menu-row <- decrement + var menu-col/edx: int <- copy left-col + menu-col <- add 2 + move-cursor screen, menu-row, menu-col + start-reverse-video screen + print-string screen, " esc " + reset-formatting screen + print-string screen, " cancel " + start-reverse-video screen + print-string screen, " enter " + reset-formatting screen + print-string screen, " define " + # draw the word, positioned appropriately around the cursor + var cursor-col/ebx: (addr int) <- get sandbox, cursor-col + var start-col/ecx: int <- copy *cursor-col + var word-ah?/edx: (addr handle word) <- get sandbox, partial-name-for-function + var word/eax: (addr word) <- lookup *word-ah? + var cursor-index/eax: int <- cursor-index word + start-col <- subtract cursor-index + var cursor-row/ebx: (addr int) <- get sandbox, cursor-row + move-cursor screen, *cursor-row, start-col + var word/eax: (addr word) <- lookup *word-ah? + print-word screen, word +} + +# Render just the words in 'line'. +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) { + # curr-word + var line/eax: (addr line) <- copy _line + var first-word-ah/eax: (addr handle word) <- get line, data + var _curr-word/eax: (addr word) <- lookup *first-word-ah + var curr-word/esi: (addr word) <- copy _curr-word + # + # loop-carried dependency + var curr-col/ecx: int <- copy left-col + # + { + compare curr-word, 0 + break-if-= +#? print-string 0, "-- word in penultimate lines: " +#? { +#? var foo/eax: int <- copy curr-word +#? print-int32-hex 0, foo +#? } +#? print-string 0, "\n" + var old-col/edx: int <- copy curr-col + move-cursor screen, curr-row, curr-col + print-word screen, curr-word + { + var max-width/eax: int <- word-length curr-word + curr-col <- add max-width + curr-col <- add 1/margin-right + } + # cache cursor column if necessary + { + compare curr-word, cursor-word + break-if-!= +#? print-string 0, "Cursor at " +#? print-int32-decimal 0, curr-row +#? print-string 0, ", " +#? print-int32-decimal 0, old-col +#? print-string 0, "\n" +#? print-string 0, "contents: " +#? print-word 0, cursor-word +#? print-string 0, "\n" +#? { +#? var foo/eax: int <- copy cursor-word +#? print-int32-hex 0, foo +#? print-string 0, "\n" +#? } + var dest/ecx: (addr int) <- copy cursor-row-addr + var src/eax: int <- copy curr-row + copy-to *dest, src + dest <- copy cursor-col-addr + copy-to *dest, old-col + var cursor-index-in-word/eax: int <- cursor-index curr-word + add-to *dest, cursor-index-in-word + } + # loop update + var next-word-ah/edx: (addr handle word) <- get curr-word, next + var _curr-word/eax: (addr word) <- lookup *next-word-ah + curr-word <- copy _curr-word + loop + } +} + +fn call-depth-at-cursor _sandbox: (addr sandbox) -> _/eax: int { + var sandbox/esi: (addr sandbox) <- copy _sandbox + var cursor-call-path/edi: (addr handle call-path-element) <- get sandbox, cursor-call-path + var result/eax: int <- call-path-element-length cursor-call-path + result <- add 2 # input-row - 1 + return result +} + +fn call-path-element-length _x: (addr handle call-path-element) -> _/eax: int { + var curr-ah/ecx: (addr handle call-path-element) <- copy _x + var result/edi: int <- copy 0 + { + var curr/eax: (addr call-path-element) <- lookup *curr-ah + compare curr, 0 + break-if-= + curr-ah <- get curr, next + result <- increment + loop + } + return result +} + +# Render the line of words in line, along with the state of the stack under each word. +# Also render any expanded function calls using recursive calls. +# +# Along the way, compute the column the cursor should be positioned at (cursor-col-addr). +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 { +#? print-string 0, "render-line\n" +#? dump-table bindings + # curr-word + var line/esi: (addr line) <- copy _line + var first-word-ah/eax: (addr handle word) <- get line, data + var curr-word/eax: (addr word) <- lookup *first-word-ah + # + # loop-carried dependency + var curr-col/ecx: int <- copy left-col + # + { + compare curr-word, 0 + break-if-= +#? print-string 0, "-- word " +#? print-word 0, curr-word +#? print-string 0, "\n" + # if necessary, first render columns for subsidiary stack + $render-line:subsidiary: { + { +#? print-string 0, "check sub\n" + var display-subsidiary-stack?/eax: boolean <- find-in-call-paths expanded-words, curr-path + compare display-subsidiary-stack?, 0/false + break-if-= $render-line:subsidiary + } +#? print-string 0, "render subsidiary stack\n" + # does function exist? + var callee/edi: (addr function) <- copy 0 + { + var callee-h: (handle function) + var callee-ah/ecx: (addr handle function) <- address callee-h + callee functions, curr-word, callee-ah + var _callee/eax: (addr function) <- lookup *callee-ah + callee <- copy _callee + compare callee, 0 + break-if-= $render-line:subsidiary + } + move-cursor screen, top-row, curr-col + start-color screen, 8, 7 + print-word screen, curr-word + { + var word-len/eax: int <- word-length curr-word + curr-col <- add word-len + curr-col <- add 2 + increment top-row + } + # obtain stack at call site + var stack-storage: value-stack + var stack/edx: (addr value-stack) <- address stack-storage + initialize-value-stack stack, 0x10 + { + var prev-word-ah/eax: (addr handle word) <- get curr-word, prev + var prev-word/eax: (addr word) <- lookup *prev-word-ah + compare prev-word, 0 + break-if-= + var bindings2-storage: table + var bindings2/ebx: (addr table) <- address bindings2-storage + deep-copy-table bindings, bindings2 + evaluate functions, bindings2, first-line, prev-word, stack + } + # construct new bindings + var callee-bindings-storage: table + var callee-bindings/esi: (addr table) <- address callee-bindings-storage + initialize-table callee-bindings, 0x10 + bind-args callee, stack, callee-bindings + # obtain body + var callee-body-ah/eax: (addr handle line) <- get callee, body + var callee-body/eax: (addr line) <- lookup *callee-body-ah + var callee-body-first-word/edx: (addr handle word) <- get callee-body, data + # - render subsidiary stack + push-to-call-path-element curr-path, callee-body-first-word # leak +#? print-string 0, "subsidiary {\n" +#? dump-table callee-bindings +#? syscall_exit + 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 +#? print-string 0, "}\n" + drop-from-call-path-element curr-path + # + move-cursor screen, top-row, curr-col + print-code-point screen, 0x21d7/⇗ + # + curr-col <- add 2 + decrement top-row + } + # render main column + var old-col/edx: int <- copy curr-col + var bindings2-storage: table + var bindings2/ebx: (addr table) <- address bindings2-storage +#? print-string 0, "deep-copy {\n" + deep-copy-table bindings, bindings2 +#? print-string 0, "}\n" +#? print-string 0, "render column {\n" + curr-col <- render-column screen, functions, bindings2, first-line, line, curr-word, top-row, curr-col +#? print-string 0, "}\n" + # cache cursor column if necessary + $render-line:cache-cursor-column: { + { + var found?/eax: boolean <- call-path-element-match? curr-path, cursor-call-path + compare found?, 0/false + break-if-= $render-line:cache-cursor-column + } + var dest/edi: (addr int) <- copy cursor-row-addr + { + var src/eax: int <- copy top-row + copy-to *dest, src + } + dest <- copy cursor-col-addr + copy-to *dest, old-col + var cursor-index-in-word/eax: int <- cursor-index curr-word + add-to *dest, cursor-index-in-word + } + # loop update +#? print-string 0, "next word\n" + var next-word-ah/edx: (addr handle word) <- get curr-word, next + curr-word <- lookup *next-word-ah +#? { +#? var foo/eax: int <- copy curr-word +#? print-int32-hex 0, foo +#? print-string 0, "\n" +#? } + increment-final-element curr-path + loop + } + return curr-col +} + +fn callee functions: (addr handle function), word: (addr word), out: (addr handle function) { + var stream-storage: (stream byte 0x10) + var stream/esi: (addr stream byte) <- address stream-storage + emit-word word, stream + find-function functions, stream, out +} + +# Render: +# - starting at top-row, left-col: final-word +# - starting somewhere below at left-col: the stack result from interpreting first-world to final-word (inclusive) +# +# Return the farthest column written. +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 { +#? print-string 0, "render-column\n" +#? dump-table bindings + var max-width/esi: int <- copy 0 + { + # compute stack + var stack: value-stack + var stack-addr/edi: (addr value-stack) <- address stack + initialize-value-stack stack-addr, 0x10/max-words + # copy bindings + var bindings2-storage: table + var bindings2/ebx: (addr table) <- address bindings2-storage +#? print-string 0, "deep copy table {\n" + deep-copy-table bindings, bindings2 +#? print-string 0, "}\n" + evaluate functions, bindings2, first-line, final-word, stack-addr + # indent stack + var indented-col/ebx: int <- copy left-col + indented-col <- add 1/margin-right + # render stack + var curr-row/edx: int <- copy top-row + curr-row <- add 2/stack-margin-top + { + var top-addr/ecx: (addr int) <- get stack-addr, top + compare *top-addr, 0 + break-if-<= + decrement *top-addr + var data-ah/eax: (addr handle array value) <- get stack-addr, data + var data/eax: (addr array value) <- lookup *data-ah + var top/ecx: int <- copy *top-addr + var dest-offset/ecx: (offset value) <- compute-offset data, top + var val/eax: (addr value) <- index data, dest-offset + render-value-at screen, curr-row, indented-col, val, 1/top-level=true + { + var width/eax: int <- value-width val, 1 + compare width, max-width + break-if-<= + max-width <- copy width + } + var height/eax: int <- value-height val + curr-row <- add height + loop + } + } + + max-width <- add 2 # spaces on either side of items on the stack + + # render word, initialize result + reset-formatting screen + move-cursor screen, top-row, left-col + print-word screen, final-word + { + var width/eax: int <- word-length final-word + compare width, max-width + break-if-<= + max-width <- copy width + } + + # post-process right-col + var right-col/ecx: int <- copy left-col + right-col <- add max-width + right-col <- add 1/margin-right +#? print-int32-decimal 0, left-col +#? print-string 0, " => " +#? print-int32-decimal 0, right-col +#? print-string 0, "\n" + return right-col +} + +fn render-function-menu _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var screen-ah/edi: (addr handle screen) <- get env, screen + var _screen/eax: (addr screen) <- lookup *screen-ah + var screen/edi: (addr screen) <- copy _screen + var nrows/eax: (addr int) <- get env, nrows + move-cursor screen, *nrows, 0 + start-reverse-video screen + print-string screen, " ctrl-q " + reset-formatting screen + print-string screen, " quit " + start-reverse-video screen + print-string screen, " ctrl-a " + reset-formatting screen + print-string screen, " ⏮ " + start-reverse-video screen + print-string screen, " ctrl-b " + reset-formatting screen + print-string screen, " ◀ word " + start-reverse-video screen + print-string screen, " ctrl-f " + reset-formatting screen + print-string screen, " word ▶ " + start-reverse-video screen + print-string screen, " ctrl-e " + reset-formatting screen + print-string screen, " ⏭ " + start-reverse-video screen + print-string screen, " ctrl-g " + reset-formatting screen + print-string screen, " go to function " + start-reverse-video screen + print-string screen, " tab " + reset-formatting screen + print-string screen, " go to sandbox" +} + +fn render-sandbox-menu _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var screen-ah/edi: (addr handle screen) <- get env, screen + var _screen/eax: (addr screen) <- lookup *screen-ah + var screen/edi: (addr screen) <- copy _screen + var nrows/eax: (addr int) <- get env, nrows + move-cursor screen, *nrows, 0 + start-reverse-video screen + print-string screen, " ctrl-q " + reset-formatting screen + print-string screen, " quit " + start-reverse-video screen + print-string screen, " ctrl-a " + reset-formatting screen + print-string screen, " ⏮ " + start-reverse-video screen + print-string screen, " ctrl-b " + reset-formatting screen + print-string screen, " ◀ word " + start-reverse-video screen + print-string screen, " ctrl-f " + reset-formatting screen + print-string screen, " word ▶ " + start-reverse-video screen + print-string screen, " ctrl-e " + reset-formatting screen + print-string screen, " ⏭ " + start-reverse-video screen + print-string screen, " ctrl-g " + reset-formatting screen + print-string screen, " go to function " + start-reverse-video screen + print-string screen, " ctrl-l " + reset-formatting screen + print-string screen, " new line " + start-reverse-video screen + print-string screen, " ctrl-u " + reset-formatting screen + print-string screen, " clear " + start-reverse-video screen + print-string screen, " ctrl-n " + reset-formatting screen + print-string screen, " name word " + start-reverse-video screen + print-string screen, " ctrl-d " + reset-formatting screen + print-string screen, " define function" +} + +fn clear-canvas _env: (addr environment) { + var env/esi: (addr environment) <- copy _env + var screen-ah/edi: (addr handle screen) <- get env, screen + var _screen/eax: (addr screen) <- lookup *screen-ah + var screen/edi: (addr screen) <- copy _screen + clear-screen screen + var nrows/eax: (addr int) <- get env, nrows + var sep-col/ecx: (addr int) <- get env, code-separator-col + # divider + draw-vertical-line screen, 1, *nrows, *sep-col + # primitives + var dummy/eax: int <- render-primitives screen, *nrows, *sep-col +} + +# return value: top-most row written to +fn render-primitives screen: (addr screen), bottom-margin-row: int, right-col: int -> _/eax: int { + # render primitives from the bottom of the screen upward + var row/ecx: int <- copy bottom-margin-row + row <- subtract 1 + var col/edx: int <- copy 1 + move-cursor screen, row, col + row, col <- render-primitive-group screen, row, col, right-col, "numbers: ", "+ - * / sqrt " + row, col <- render-primitive-group screen, row, col, right-col, "arrays: ", "len " + row, col <- render-primitive-group screen, row, col, right-col, "files: ", "open read slurp lines " + row, col <- render-primitive-group screen, row, col, right-col, "misc: ", "dup swap " # hack: keep these at the right of the bottom row + row, col <- render-primitive-group screen, row, col, right-col, "screens: ", "fake-screen print move up down left right " + # finally print heading up top + row <- decrement + move-cursor screen, row, 1 + start-bold screen + print-string screen, "primitives:" + reset-formatting screen + return row +} + +# start at row, col and print the given strings +# move up one row if there isn't enough room before right-col +# return row, col printed until +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 { + var row/ecx: int <- copy _row + var col/edx: int <- copy _col + # decrement row if necessary + var new-col/ebx: int <- copy col + var heading/esi: (addr array byte) <- copy _heading + var len1/eax: int <- length heading + new-col <- add len1 + var contents/edi: (addr array byte) <- copy _contents + var len2/eax: int <- length contents + new-col <- add len2 + var bound/eax: int <- copy right-col + bound <- decrement + { + compare new-col, bound + break-if-<= + row <- decrement + col <- copy 1 + } + move-cursor screen, row, col + start-color screen, 0xf6, 7 + print-string screen, heading + reset-formatting screen + print-string screen, contents + return row, new-col +} + +fn render-functions screen: (addr screen), right-col: int, _env: (addr environment) { + var row/ecx: int <- copy 1 + var dummy-col/edx: int <- copy right-col + var env/esi: (addr environment) <- copy _env + var functions/esi: (addr handle function) <- get env, functions + { + var curr/eax: (addr function) <- lookup *functions + compare curr, 0 + break-if-= + row, dummy-col <- render-function-right-aligned screen, row, right-col, curr + functions <- get curr, next + row <- add 1/inter-function-margin + loop + } +} + +# print function starting at row, right-aligned before right-col +# return row, col printed until +fn render-function-right-aligned screen: (addr screen), row: int, right-col: int, f: (addr function) -> _/ecx: int, _/edx: int { + var col/edx: int <- copy right-col + col <- subtract 1/function-right-margin + var col2/ebx: int <- copy col + var width/eax: int <- function-width f + col <- subtract width + var new-row/ecx: int <- copy row + var height/eax: int <- function-height f + new-row <- add height + new-row <- decrement + col <- subtract 1/function-left-padding + start-color screen, 0, 0xf7 + clear-rect screen, row, col, new-row, col2 + col <- add 1 +#? var dummy/eax: grapheme <- read-key-from-real-keyboard + render-function screen, row, col, f + new-row <- add 1/function-bottom-margin + col <- subtract 1/function-left-padding + col <- subtract 1/function-left-margin + reset-formatting screen + return new-row, col +} + +# render function starting at row, col +# only single-line functions supported for now +fn render-function screen: (addr screen), row: int, col: int, _f: (addr function) { + var f/esi: (addr function) <- copy _f + var args/ecx: (addr handle word) <- get f, args + move-cursor screen, row, col + print-words-in-reverse screen, args + var name-ah/eax: (addr handle array byte) <- get f, name + var name/eax: (addr array byte) <- lookup *name-ah + start-bold screen + print-string screen, name + reset-formatting screen + start-color screen, 0, 0xf7 + increment row + add-to col, 2 + move-cursor screen, row, col + print-string screen, "≡ " + add-to col, 2 + var cursor-row/ecx: (addr int) <- get f, cursor-row + var cursor-col/edx: (addr int) <- get f, cursor-col + var cursor-word-ah/eax: (addr handle word) <- get f, cursor-word + var _cursor-word/eax: (addr word) <- lookup *cursor-word-ah + var cursor-word/ebx: (addr word) <- copy _cursor-word + var body-ah/eax: (addr handle line) <- get f, body + var body/eax: (addr line) <- lookup *body-ah + render-line-without-stack screen, body, row, col, cursor-word, cursor-row, cursor-col +} + +fn real-grapheme? g: grapheme -> _/eax: boolean { + # if g == newline return true + compare g, 0xa + { + break-if-!= + return 1/true + } + # if g == tab return true + compare g, 9 + { + break-if-!= + return 1/true + } + # if g < 32 return false + compare g, 0x20 + { + break-if->= + return 0/false + } + # if g <= 255 return true + compare g, 0xff + { + break-if-> + return 1/true + } + # if (g&0xff == Esc) it's an escape sequence + and-with g, 0xff + compare g, 0x1b/esc + { + break-if-!= + return 0/false + } + # otherwise return true + return 1/true +} |