diff options
| -rw-r--r-- | quine.asm | 160 |
1 files changed, 158 insertions, 2 deletions
diff --git a/quine.asm b/quine.asm index 55af67d..9ffb10c 100644 --- a/quine.asm +++ b/quine.asm @@ -7196,6 +7196,23 @@ cold_start: dq swap, litstring, ";", early_find, roll3, execute + ; Although we will eventually define the word "'" to give us the symbol of a + ; word, it will rely on being able to compile a literal. Rather than do + ; lots of string processing later, we choose to define this word now to + ; avoid having to look up the word "lit" as part of that. + ; + ; In: + ; value + dq litstring, "literal", early_create, early_docol_codeword + dq litstring, "lit", early_find, entry_to_execution_token, early_comma + dq litstring, "lit", early_find, entry_to_execution_token, early_comma + dq litstring, ",", early_find, entry_to_execution_token, early_comma + dq litstring, ",", early_find, entry_to_execution_token, early_comma + dq litstring, "exit", early_find, entry_to_execution_token, early_comma + dq early_here, fetch, lit, 8, packalign, early_here_store + + + ; Now the single most important word... dq litstring, "interpret", early_create, early_docol_codeword dq litstring, "word", early_find, entry_to_execution_token, early_comma @@ -8224,7 +8241,7 @@ defword fetch_value_stack, 0 ; Also, the "c" was meant to indicate that it works at one-byte granularity, ; but that isn't, uh... actually an important property here, and as a blanket ; call we're not using letters to denote data sizes. So we call it "memcopy". -; Apologies to C programmers but vowels are good, actually. +; Apologies to the C programming tradition but vowels are good, actually. ; ; Jonesforth also offers C@C! as another name for its CCOPY, but neither ; "@!" nor "mem@mem!" seems particulaly nice. @@ -8247,6 +8264,46 @@ defword memcopy, 0 mov.qreg.qreg rsi, rdx next +; (written much later) +; +; In: +; destination +; source +; length +defword memmove, 0 + dq $ + 8 + ; We need to save and restore rsi; the other registers we can trample. + mov.qreg.qreg rdx, rsi + pop.qreg rcx + pop.qreg rsi + pop.qreg rdi + + ; We need to check source < destination to decide which end to start from. + mov.qreg.qreg rax, rsi + cmp.qreg.qreg rax, rdi + ; Relative offsets are from the start of the instruction after the jmp. + jmp.cc.rel.bimm below, 4 + + ; If source is greater, we are sliding downwards so we start from the low + ; end. So, we get to leave the DF flag alone. + rep movsb ; 2 bytes + jmp.rel.bimm 16 ; 2 bytes + + ; If destination is greater, we are sliding upwards so we start from the + ; high end. So, we have to save and restore DF. Also, we have to adjust the + ; pointers. + add.qreg.qreg rsi, rcx ; 3 bytes + dec.qreg rsi ; 3 bytes + add.qreg.qreg rdi, rcx ; 3 bytes + dec.qreg rdi ; 3 bytes + std ; 1 byte + rep movsb ; 2 bytes + cld ; 1 byte + + mov.qreg.qreg rsi, rdx + next + + ; Stack in: ; string address ; Stack out: @@ -10997,8 +11054,107 @@ defword boot_source, 0x40 ; multiple of eight bytes long; any accidental null-padding that ; flatassembler inserts will be treated as a string terminator by ; attach-string-to-input-buffer. - dq "0 sys-exit " + + ; In general, we're going to want to be able to go on little excursions + ; where we define utility words that are only useful for one task, then + ; deallocate that stuff after we're done with it. We implement "forget", + ; which removes both dictionary entries and heap allocations for the entry + ; pointer it's given and everything that came after. + ; + ; The implementation strategy is the same as Jonesforth's version, but + ; Jonesforth runs in immediate mode and reads a word to operate on, whereas + ; ours takes an entry pointer and runs in either compiled or immediate + ; modes. + ; + ; In: + ; entry pointer + dq ": forget dup @ latest ! here ! ; " + + ; We'll be defining a lot of immediate words, so we should set up a terse + ; way to do that. + dq ": make-immediate latest @ set-word-immediate ; " + dq ": make-hidden latest @ hide-entry ; " + + ; The word "'" quotes the following word, looking it up and treating it as + ; a constant. In immediate mode, the constant winds up on the stack; in + ; compile mode it gets compiled. + ; + ; There are a few possible implementation strategies here. Running as an + ; immediate word means there's a clear and unambiguous concept of "the + ; following word", so that's what we do; otherwise we'd have to get clever + ; about somehow finding out where we were called from. That means we take on + ; what would otherwise be the interpreter's responsibility, of checking what + ; mode we're in. Happily, that's easy to do. + ; + ; Though it might be nice to have high-level flow control for this, our + ; implementation of "if" below relies on "'" several times, whereas "'" only + ; branches once. So we bootstrap "'" first. + dq ": ' word value@ find dropstring-with-result " + dq " interpreter-flags @ 1 and 0branch [ 2 8 * , ] literal " + dq " ; make-immediate " + + ; Sooner or later we'll want to define recursive words; this one lets us + ; do that. It compiles into a call to the word that's currently being + ; defined (strictly speaking, the one whose definition was most recently + ; begun). + dq ": recurse latest @ entry-to-execution-token , ; make-immediate " + + ; We use a novel suffix-based approach to flow control. We define words + ; { and } which describe the boundaries of blocks of code, leaving a + ; description on the value stack, while still compiling the contents + ; normally. + ; + ; Then follow-up words such as "if" can use that information to slide + ; the blocks around and insert any needed branches and other logic. + ; + ; After compiling a { ... } block, the stack is (start pointer, length). + dq ": { here @ ; make-immediate " + dq ": } dup here @ swap - ; make-immediate " + + ; (start pointer, length) + dq ": if dup2 swap dup 5 8 * + 3unroll swap " + ; (start pointer, length, start pointer, adjusted start pointer, length) + dq " memmove " + ; (start pointer, length) + dq " swap here @ swap here ! swap " + ; (old here, length) + dq " ' lit entry-to-execution-token , 0 , " + dq " ' = entry-to-execution-token , " + ; The branch length needs to be one word longer than the block length, + ; because the length field itself is part of the scope of the branch. + dq " ' 0branch entry-to-execution-token , dup 8 + , " + ; (old here, length) + dq " drop 5 8 * + here ! ; make-immediate " + + dq ": foo 5 6 < { 42 . } if ; foo " + dq " " + + ; TODO define if/then/else + ; TODO define begin/until/again and repeat, or something like them + ; TODO consider defining case / endcase + ; TODO define ( ... ) comments + ; TODO define negate, true, false, not + ; TODO define constant (double-check variable) + ; TODO consider defining "value" and "to" + ; TODO consider defining "id" + ; TODO consider defining is-hidden and is-immediate + ; TODO consider defining 'word and "words" + ; TODO define ? + ; TODO define allot and cells + ; TODO consider what text stuff to define + ; TODO figure out a comment syntax + ; TODO fix the "describe" words + ; TODO stack trace + ; TODO make an interactive debugger + ; TODO argc argv envp etc + ; TODO consider "bye" and "unused" + ; TODO consider file API + ; TODO consider ";asm" or something + ; TODO consider a welcome message + + ; If we get to this point, clean up and leave. + dq "0 sys-exit " dq 0 |