The program:
( buffering input ) {{ 64 constant width 4 constant lines variable num create buffer lines width * allot : incr ( - ) num dup @ lines 1- >if 0 swap ! ;then ++ ; : this ( -$ ) num @ width * buffer + ; : "accept" ( n"- ) accept tib dup getLength incr this swap 1+ copy ; ---reveal--- : input ( "- ) whitespace off 10 "accept" whitespace on ; : echo ( "-" ) input this type ; : listen ( "- ) input this dup getLength eval ; }}
The breakdown:
{{ ::: Start local dictionary space. 64 constant width ::: Lines may be up to 64 chars in length. 4 constant lines ::: Number of lines that will be stored. variable num ::: Which line we are looking at. create buffer ::: Pointer to the beginning of the first line lines width * allot ::: Make room on the stack for 4 * 64 characters
We have just created a list of lines (buffer) with an index (num). However, we want to be able to access it conveniently. The best structure for our purposes is a circular list. So all we need to do is make sure num is set back to zero if it is ever equal to one less than lines.
: incr ( - ) num dup @ lines 1- >if 0 swap ! ;then ++ ; : this ( -$ ) num @ width * buffer + ;
incr alters num to either increase its value by one, or if it gets too high, set it to zero. this uses num to calculate the actual address of the line we are looking at. The returned address is somewhat like the tib where "accept" is concerned -- a place where parsed strings are sent.
: "accept" ( n"- ) accept tib dup getLength ::: create a counted string of the input incr this swap ::: prepare the next line, move length to tos 1+ copy ; ::: copy string, including the null character
"accept" is a word which behaves much like accept but writes to our custom buffer instead of using the tib. It starts by calling accept, then copies what that places at tib to an address in our list specified by this. Before it does this, it calls incr so the previous call of "accept" remains in the buffer space that was previously used.
---reveal--- ::: begin defining public words : input ( "- ) ::: accept a line of input to the buffer whitespace off ::: turn off whitespace filtering 10 "accept" ::: parse for the enter key whitespace on ; ::: whitespace filtering back on
input is a word that makes use of "accept" to read a line of input into the buffer. Based on this we can make custom words that re-type the same line, or which pass it to eval.
: echo ( "-" ) input this type ; : listen ( "- ) input this dup getLength eval ; }}
echo simply reads a line, uses this to get its address in the buffer, and uses type to display it to the screen. listen uses dup getLength to convert to a counted string and passes it to eval so that it is much like simply typing the line into the interpreter. The exception is that the evaluation is delayed, so you can fix typos involving spaces. Finally, the private words are hidden from the dictionary using }}.