80ab395823
Change the mainloop so that words are executed immediately after they're read. This greatly simplifies execution model and allow the "DEFINE" word to become an IMMEDIATE and stop its "copy from compiled words" scheme. The downside to this is that flow control words no longer work when being used directly in the input buffer. They only work as part of a definition. It also broke "RECURSE", but I've replaced it with "BEGIN" and "AGAIN". Another effect of this change is that definitions can now span multiple lines. All in all, it feels good to get rid of that COMPBUF...
141 lines
6.3 KiB
Plaintext
141 lines
6.3 KiB
Plaintext
Stack notation: "<stack before> -- <stack after>". Rightmost is top of stack
|
|
(TOS). For example, in "a b -- c d", b is TOS before, d is TOS after. "R:" means
|
|
that the Return Stack is modified. "I:" prefix means "IMMEDIATE", that is, that
|
|
this stack transformation is made at compile time.
|
|
|
|
DOES>: Used inside a colon definition that itself uses CREATE, DOES> transforms
|
|
that newly created word into a "does cell", that is, a regular cell ( when
|
|
called, puts the cell's addr on PS), but right after that, it executes words
|
|
that appear after the DOES>.
|
|
|
|
"does cells" always allocate 4 bytes (2 for the cell, 2 for the DOES> link) and
|
|
there is no need for ALLOT in colon definition.
|
|
|
|
At compile time, colon definition stops processing words when reaching the
|
|
DOES>.
|
|
|
|
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"
|
|
|
|
Word references (wordref): When we say we have a "word reference", it's a
|
|
pointer to a words *code link*. For example, the label "PLUS:" in this unit is a
|
|
word reference. Why not refer to the beginning of the word struct? Because we
|
|
actually seldom refer to the name and prev link, except during compilation, so
|
|
defining "word reference" this way makes the code easier to understand.
|
|
|
|
Atom: A word of the type compiledWord contains, in its PF, a list of what we
|
|
call "atoms". Those atoms are most of the time word references, but they can
|
|
also be references to NUMBER and LIT.
|
|
|
|
Words between "()" are "support words" that aren't really meant to be used
|
|
directly, but as part of another word.
|
|
|
|
"*I*" in description indicates an IMMEDIATE word.
|
|
|
|
*** Defining words ***
|
|
: x ... -- Define a new word
|
|
; R:I -- Exit a colon definition
|
|
, n -- Write n in HERE and advance it.
|
|
' x -- a Push addr of word x to a.
|
|
['] x -- *I* Like "'", but spits the addr as a number
|
|
literal.
|
|
( -- *I* Comment. Ignore rest of line until ")" is read.
|
|
ALLOT n -- Move HERE by n bytes
|
|
C, b -- Write byte b in HERE and advance it.
|
|
CREATE x -- Create cell named x. Doesn't allocate a PF.
|
|
COMPILE x -- Compile word x and write it to HERE
|
|
CONSTANT x n -- Creates cell x that when called pushes its value
|
|
DOES> -- See description at top of file
|
|
IMMEDIATE -- Flag the latest defined word as immediate.
|
|
LITN n -- *I* Inserts number from TOS as a literal
|
|
VARIABLE c -- Creates cell x with 2 bytes allocation.
|
|
|
|
*** Flow ***
|
|
Note about flow words: flow words can only be used in definitions. In the
|
|
INTERPRET loop, they don't have the desired effect because each word from the
|
|
input stream is executed immediately. In this context, branching doesn't work.
|
|
|
|
(fbr) -- Branches forward by the number specified in its
|
|
atom's cell.
|
|
(bbr) -- Branches backward by the number specified in its
|
|
atom's cell.
|
|
AGAIN I:a -- *I* Jump backwards to preceeding BEGIN.
|
|
BEGIN -- I:a *I* Marker for backward branching with AGAIN.
|
|
ELSE I:a -- *I* Compiles a (fbr) and set branching cell at a.
|
|
EXECUTE a -- Execute wordref at addr a
|
|
IF -- I:a *I* Compiles a (fbr?) and pushes its cell's addr
|
|
INTERPRET -- Get a line from stdin, compile it in tmp memory,
|
|
then execute the compiled contents.
|
|
QUIT R:drop -- Return to interpreter prompt immediately
|
|
RECURSE R:I -- R:I-2 Run the current word again.
|
|
SKIP? f -- If f is true, skip the execution of the next atom.
|
|
Use this right before ";" and you're gonna have a
|
|
bad time.
|
|
THEN I:a -- *I* Set branching cell at a.
|
|
|
|
*** Parameter Stack ***
|
|
DROP a --
|
|
DUP a -- a a
|
|
OVER a b -- a b a
|
|
SWAP a b -- b a
|
|
2DUP a b -- a b a b
|
|
2OVER a b c d -- a b c d a b
|
|
2SWAP a b c d -- c d a b
|
|
|
|
*** Return Stack ***
|
|
>R n -- R:n Pops PS and push to RS
|
|
R> R:n -- n Pops RS and push to PS
|
|
I -- n Copy RS TOS to PS
|
|
I' -- n Copy RS second item to PS
|
|
J -- n Copy RS third item to PS
|
|
|
|
*** Memory ***
|
|
@ a -- n Set n to value at address a
|
|
! n a -- Store n in address a
|
|
? a -- Print value of addr a
|
|
+! n a -- Increase value of addr a by n
|
|
C@ a -- c Set c to byte at address a
|
|
C! c a -- Store byte c in address a
|
|
CURRENT -- a Set a to wordref of last added entry.
|
|
HERE -- a Push HERE's address
|
|
H -- a HERE @
|
|
|
|
*** Arithmetic ***
|
|
|
|
+ a b -- c a + b -> c
|
|
- a b -- c a - b -> c
|
|
-^ a b -- c b - a -> c
|
|
* a b -- c a * b -> c
|
|
/ a b -- c a / b -> c
|
|
|
|
*** Logic ***
|
|
= n1 n2 -- f Push true if n1 == n2
|
|
< n1 n2 -- f Push true if n1 < n2
|
|
> n1 n2 -- f Push true if n1 > n2
|
|
CMP n1 n2 -- n Compare n1 and n2 and set n to -1, 0, or 1.
|
|
n=0: a1=a2. n=1: a1>a2. n=-1: a1<a2.
|
|
NOT f -- f Push the logical opposite of f
|
|
|
|
*** Strings ***
|
|
LITS x -- a Read following LIT and push its addr to a
|
|
SCMP a1 a2 -- n Compare strings a1 and a2. See CMP
|
|
|
|
*** I/O ***
|
|
|
|
A little word about inputs. There are two kind of inputs: direct and buffered.
|
|
As a general rule, we read line in a buffer, then feed words in it to the
|
|
interpreter. That's what "WORD" does. If it's at the End Of Line, it blocks and
|
|
wait until another line is entered.
|
|
|
|
KEY input, however, is direct. Regardless of the input buffer's state, KEY will
|
|
return the next typed key.
|
|
|
|
. n -- Print n in its decimal form
|
|
EMIT c -- Spit char c to output stream
|
|
IN> -- a Address of variable containing current pos in input
|
|
buffer.
|
|
KEY -- c Get char c from direct input
|
|
PC! c a -- Spit c to port a
|
|
PC@ a -- c Fetch c from port a
|
|
WORD -- a Read one word from buffered input and push its addr
|
|
|