X-Git-Url: http://git.annexia.org/?p=jonesforth.git;a=blobdiff_plain;f=jonesforth.S;h=44e837be3e56c47d6e9010934f9ebe4cc50b100b;hp=5d1c5834eefc3dfc33bda068fe423ba3fc14fe30;hb=c5eb13093afa5e66283806b8eb95cd3c9648a585;hpb=bc909ded4fac01d9612315ba53bc1286f63ac76e diff --git a/jonesforth.S b/jonesforth.S index 5d1c583..44e837b 100644 --- a/jonesforth.S +++ b/jonesforth.S @@ -1,11 +1,11 @@ /* A sometimes minimal FORTH compiler and tutorial for Linux / i386 systems. -*- asm -*- By Richard W.M. Jones http://annexia.org/forth This is PUBLIC DOMAIN (see public domain release statement below). - $Id: jonesforth.S,v 1.22 2007-09-23 19:40:40 rich Exp $ + $Id: jonesforth.S,v 1.29 2007-09-24 00:37:01 rich Exp $ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S */ - .set JONES_VERSION,22 + .set JONES_VERSION,29 /* INTRODUCTION ---------------------------------------------------------------------- @@ -115,20 +115,15 @@ assemble and run the code (save this file as 'jonesforth.S') are: gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S - ./jonesforth - - You will see lots of 'Warning: unterminated string; newline inserted' messages from the - assembler. That's just because the GNU assembler doesn't have a good syntax for multi-line - strings (or rather it used to, but the developers removed it!) so I've abused the syntax - slightly to make things readable. Ignore these warnings. + cat jonesforth.f - | ./jonesforth If you want to run your own FORTH programs you can do: - ./jonesforth < myprog.f + cat jonesforth.f myprog.f | ./jonesforth If you want to load your own FORTH code and then continue reading user commands, you can do: - cat myfunctions.f - | ./jonesforth + cat jonesforth.f myfunctions.f - | ./jonesforth ASSEMBLER ---------------------------------------------------------------------- @@ -579,17 +574,29 @@ cold_start: // High-level code without a codeword. .bss /* FORTH return stack. */ -#define RETURN_STACK_SIZE 8192 + .set RETURN_STACK_SIZE,8192 .align 4096 .space RETURN_STACK_SIZE return_stack: // Initial top of return stack. /* The user definitions area: space for user-defined words and general memory allocations. */ -#define USER_DEFS_SIZE 16384 + .set USER_DEFS_SIZE,65536 .align 4096 user_defs_start: .space USER_DEFS_SIZE +/* This is used as a temporary input buffer when reading from files or the terminal. */ + .set BUFFER_SIZE,4096 + .align 4096 +buffer: +_initbufftop: + .space BUFFER_SIZE +buffend: +currkey: + .int buffer +bufftop: + .int _initbufftop + /* BUILT-IN WORDS ---------------------------------------------------------------------- @@ -634,9 +641,9 @@ DOUBLE: .int DOCOL // codeword */ /* Flags - these are discussed later. */ -#define F_IMMED 0x80 -#define F_HIDDEN 0x20 -#define F_LENMASK 0x1f // length mask + .set F_IMMED,0x80 + .set F_HIDDEN,0x20 + .set F_LENMASK,0x1f // length mask // Store the chain of links. .set link,0 @@ -921,7 +928,7 @@ code_\label : // assembler code follows xorl %eax,(%esp) NEXT - defcode "INVERT",6,,INVERT // this is the FORTH "NOT" function + defcode "INVERT",6,,INVERT // this is the FORTH bitwise "NOT" function notl (%esp) NEXT @@ -1091,6 +1098,7 @@ var_\name : _Y _Z S0 Stores the address of the top of the parameter stack. + BASE The current base for printing and reading numbers. */ defvar "STATE",5,,STATE @@ -1100,6 +1108,7 @@ var_\name : defvar "_Y",2,,TY defvar "_Z",2,,TZ defvar "S0",2,,SZ + defvar "BASE",4,,BASE,10 /* BUILT-IN CONSTANTS ---------------------------------------------------------------------- @@ -1330,12 +1339,7 @@ _WORD: 5: .space 32 /* - . (also called DOT) prints the top of the stack as an integer. In real FORTH implementations - it should print it in the current base, but this assembler version is simpler and can only - print in base 10. - - Remember that you can override even built-in FORTH words easily, so if you want to write a - more advanced DOT then you can do so easily at a later point, and probably in FORTH. + . (also called DOT) prints the top of the stack as an integer in the current BASE. */ defcode ".",1,,DOT @@ -1343,23 +1347,24 @@ _WORD: call _DOT // Easier to do this recursively ... NEXT _DOT: - mov $10,%ecx // Base 10 + mov var_BASE,%ecx // Get current BASE 1: - cmp %ecx,%eax + cmp %ecx,%eax // %eax < BASE? If so jump to print immediately. jb 2f xor %edx,%edx // %edx:%eax / %ecx -> quotient %eax, remainder %edx idivl %ecx - pushl %edx + pushl %edx // Print quotient (top half) first ... call _DOT - popl %eax + popl %eax // ... then loop to print remainder jmp 1b -2: - xor %ah,%ah - aam $10 - cwde - addl $'0',%eax +2: // %eax < BASE so print immediately. + movl $digits,%edx + addl %eax,%edx + movb (%edx),%al // Note top bits are already zero. call _EMIT ret + .section .rodata +digits: .ascii "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* Almost the opposite of DOT (but not quite), SNUMBER parses a numeric string such as one returned @@ -1778,7 +1783,7 @@ _COMMA: defword ":",1,,COLON .int CREATE // CREATE the dictionary entry / header .int LIT, DOCOL, COMMA // Append DOCOL (the codeword). - .int HIDDEN // Make the word hidden (see below for definition). + .int LATEST, FETCH, HIDDEN // Make the word hidden (see below for definition). .int RBRAC // Go into compile mode. .int EXIT // Return from the function. @@ -1788,7 +1793,7 @@ _COMMA: defword ";",1,F_IMMED,SEMICOLON .int LIT, EXIT, COMMA // Append EXIT (so the word will return). - .int HIDDEN // Toggle hidden flag -- unhide the word (see below for definition). + .int LATEST, FETCH, HIDDEN // Toggle hidden flag -- unhide the word (see below for definition). .int LBRAC // Go back to IMMEDIATE mode. .int EXIT // Return from the function. @@ -1827,13 +1832,27 @@ _COMMA: NEXT /* - HIDDEN toggles the other flag, F_HIDDEN, of the latest word. Note that words flagged - as hidden are defined but cannot be called, so this is only used when you are trying to - hide the word as it is being defined. + 'addr HIDDEN' toggles the hidden flag (F_HIDDEN) of the word defined at addr. To hide the + most recently defined word (used above in : and ; definitions) you would do: + + LATEST @ HIDDEN + + Setting this flag stops the word from being found by FIND, and so can be used to make 'private' + words. For example, to break up a large word into smaller parts you might do: + + : SUB1 ... subword ... ; + : SUB2 ... subword ... ; + : SUB3 ... subword ... ; + : MAIN ... defined in terms of SUB1, SUB2, SUB3 ... ; + WORD SUB1 FIND HIDDEN \ Hide SUB1 + WORD SUB2 FIND HIDDEN \ Hide SUB2 + WORD SUB3 FIND HIDDEN \ Hide SUB3 + + After this, only MAIN is 'exported' or seen by the rest of the program. */ defcode "HIDDEN",6,,HIDDEN - movl var_LATEST,%edi // LATEST word. + pop %edi // Dictionary entry. addl $4,%edi // Point to name/flags byte. xorb $F_HIDDEN,(%edi) // Toggle the HIDDEN bit. NEXT @@ -1926,8 +1945,8 @@ _COMMA: /* PRINTING STRINGS ---------------------------------------------------------------------- - LITSTRING and EMITSTRING are primitives used to implement the ." operator (which is - written in FORTH). See the definition of that operator below. + LITSTRING and EMITSTRING are primitives used to implement the ." and S" operators + (which are written in FORTH). See the definition of those operators below. */ defcode "LITSTRING",9,,LITSTRING @@ -2060,561 +2079,11 @@ interpret_is_lit: words can be written as FORTH itself, including words like IF, THEN, .", etc which in most languages would be considered rather fundamental. - As a kind of trick, I prefill the input buffer with the initial FORTH code. Once this code - has run (when we get to the "OK" prompt), this input buffer is reused for reading any further - user input. - - Some notes about the code: - - \ (backslash) is the FORTH way to start a comment which goes up to the next newline. However - because this is a C-style string, I have to escape the backslash, which is why they appear as - \\ comment. - - Similarly, any backslashes in the code are doubled, and " becomes \" (eg. the definition of ." - is written as : .\" ... ;) + I used to append this here in the assembly file, but I got sick of fighting against gas's + stupid (lack of) multiline string syntax. So now that is in a separate file called jonesforth.f - I use indenting to show structure. The amount of whitespace has no meaning to FORTH however - except that you must use at least one whitespace character between words, and words themselves - cannot contain whitespace. - - FORTH is case-sensitive. Use capslock! - - Enjoy! + If you don't already have that file, download it from http://annexia.org/forth in order + to continue the tutorial. */ - .data - .align 4096 -buffer: - // Multi-line constant gives 'Warning: unterminated string; newline inserted' messages which you can ignore. - .ascii "\ -\\ Define some character constants -: '\\n' 10 ; -: 'SPACE' 32 ; - -\\ CR prints a carriage return -: CR '\\n' EMIT ; - -\\ SPACE prints a space -: SPACE 'SPACE' EMIT ; - -\\ DUP, DROP are defined in assembly for speed, but this is how you might define them -\\ in FORTH. Notice use of the scratch variables _X and _Y. -\\ : DUP _X ! _X @ _X @ ; -\\ : DROP _X ! ; - -\\ The 2... versions of the standard operators work on pairs of stack entries. They're not used -\\ very commonly so not really worth writing in assembler. Here is how they are defined in FORTH. -: 2DUP OVER OVER ; -: 2DROP DROP DROP ; - -\\ More standard FORTH words. -: 2* 2 * ; -: 2/ 2 / ; - -\\ The primitive . (DOT) function doesn't follow with a blank, so redefine it to behave like FORTH. -\\ Notice how we can trivially redefine existing words. Word definitions are not recursive by -\\ default, but see below for the RECURSE word. -: . - . SPACE \\ call built-in DOT, then print a space. -; - -\\ LITERAL takes whatever is on the stack and compiles LIT -: LITERAL IMMEDIATE - ' LIT , \\ compile LIT - , \\ compile the literal itself (from the stack) - ; - -\\ Now we can use [ and ] to insert literals which are calculated at compile time. -\\ Within definitions, use [ ... ] LITERAL anywhere that '...' is a constant expression which you -\\ would rather only compute once (at compile time, rather than calculating it each time your word runs). -: ':' - [ \\ go into immediate mode temporarily - CHAR : \\ push the number 58 (ASCII code of colon) on the stack - ] \\ go back to compile mode - LITERAL \\ compile LIT 58 as the definition of ':' word -; - -\\ A few more character constants defined the same way as above. -: '(' [ CHAR ( ] LITERAL ; -: ')' [ CHAR ) ] LITERAL ; -: '\"' [ CHAR \" ] LITERAL ; - -\\ So far we have defined only very simple definitions. Before we can go further, we really need to -\\ make some control structures, like IF ... THEN and loops. Luckily we can define arbitrary control -\\ structures directly in FORTH. -\\ -\\ Please note that the control structures as I have defined them here will only work inside compiled -\\ words. If you try to type in expressions using IF, etc. in immediate mode, then they won't work. -\\ Making these work in immediate mode is left as an exercise for the reader. - -\\ condition IF true-part THEN rest -\\ -- compiles to: --> condition 0BRANCH OFFSET true-part rest -\\ where OFFSET is the offset of 'rest' -\\ condition IF true-part ELSE false-part THEN -\\ -- compiles to: --> condition 0BRANCH OFFSET true-part BRANCH OFFSET2 false-part rest -\\ where OFFSET if the offset of false-part and OFFSET2 is the offset of rest - -\\ IF is an IMMEDIATE word which compiles 0BRANCH followed by a dummy offset, and places -\\ the address of the 0BRANCH on the stack. Later when we see THEN, we pop that address -\\ off the stack, calculate the offset, and back-fill the offset. -: IF IMMEDIATE - ' 0BRANCH , \\ compile 0BRANCH - HERE @ \\ save location of the offset on the stack - 0 , \\ compile a dummy offset -; - -: THEN IMMEDIATE - DUP - HERE @ SWAP - \\ calculate the offset from the address saved on the stack - SWAP ! \\ store the offset in the back-filled location -; - -: ELSE IMMEDIATE - ' BRANCH , \\ definite branch to just over the false-part - HERE @ \\ save location of the offset on the stack - 0 , \\ compile a dummy offset - SWAP \\ now back-fill the original (IF) offset - DUP \\ same as for THEN word above - HERE @ SWAP - - SWAP ! -; - -\\ BEGIN loop-part condition UNTIL -\\ -- compiles to: --> loop-part condition 0BRANCH OFFSET -\\ where OFFSET points back to the loop-part -\\ This is like do { loop-part } while (condition) in the C language -: BEGIN IMMEDIATE - HERE @ \\ save location on the stack -; - -: UNTIL IMMEDIATE - ' 0BRANCH , \\ compile 0BRANCH - HERE @ - \\ calculate the offset from the address saved on the stack - , \\ compile the offset here -; - -\\ BEGIN loop-part AGAIN -\\ -- compiles to: --> loop-part BRANCH OFFSET -\\ where OFFSET points back to the loop-part -\\ In other words, an infinite loop which can only be returned from with EXIT -: AGAIN IMMEDIATE - ' BRANCH , \\ compile BRANCH - HERE @ - \\ calculate the offset back - , \\ compile the offset here -; - -\\ BEGIN condition WHILE loop-part REPEAT -\\ -- compiles to: --> condition 0BRANCH OFFSET2 loop-part BRANCH OFFSET -\\ where OFFSET points back to condition (the beginning) and OFFSET2 points to after the whole piece of code -\\ So this is like a while (condition) { loop-part } loop in the C language -: WHILE IMMEDIATE - ' 0BRANCH , \\ compile 0BRANCH - HERE @ \\ save location of the offset2 on the stack - 0 , \\ compile a dummy offset2 -; - -: REPEAT IMMEDIATE - ' BRANCH , \\ compile BRANCH - SWAP \\ get the original offset (from BEGIN) - HERE @ - , \\ and compile it after BRANCH - DUP - HERE @ SWAP - \\ calculate the offset2 - SWAP ! \\ and back-fill it in the original location -; - -\\ FORTH allows ( ... ) as comments within function definitions. This works by having an IMMEDIATE -\\ word called ( which just drops input characters until it hits the corresponding ). -: ( IMMEDIATE - 1 \\ allowed nested parens by keeping track of depth - BEGIN - KEY \\ read next character - DUP '(' = IF \\ open paren? - DROP \\ drop the open paren - 1+ \\ depth increases - ELSE - ')' = IF \\ close paren? - 1- \\ depth decreases - THEN - THEN - DUP 0= UNTIL \\ continue until we reach matching close paren, depth 0 - DROP \\ drop the depth counter -; - -( - From now on we can use ( ... ) for comments. - - In FORTH style we can also use ( ... -- ... ) to show the effects that a word has on the - parameter stack. For example: - - ( n -- ) means that the word consumes an integer (n) from the parameter stack. - ( b a -- c ) means that the word uses two integers (a and b, where a is at the top of stack) - and returns a single integer (c). - ( -- ) means the word has no effect on the stack -) - -( With the looping constructs, we can now write SPACES, which writes n spaces to stdout. ) -: SPACES ( n -- ) - BEGIN - DUP 0> ( while n > 0 ) - WHILE - SPACE ( print a space ) - 1- ( until we count down to 0 ) - REPEAT - DROP -; - -( .S prints the contents of the stack. Very useful for debugging. ) -: .S ( -- ) - DSP@ ( get current stack pointer ) - BEGIN - DUP S0 @ < - WHILE - DUP @ . ( print the stack element ) - 4+ ( move up ) - REPEAT - DROP -; - -( DEPTH returns the depth of the stack. ) -: DEPTH ( -- n ) - S0 @ DSP@ - - 4- ( adjust because S0 was on the stack when we pushed DSP ) -; - -( - [NB. The following may be a bit confusing because of the need to use backslash before - each double quote character. The backslashes are there to keep the assembler happy. - They are NOT part of the final output. So here we are defining a function called - 'dot double-quote' (not 'dot backslash double-quote').] - - .\" is the print string operator in FORTH. Example: .\" Something to print\" - The space after the operator is the ordinary space required between words. - - This is tricky to define because it has to do different things depending on whether - we are compiling or in immediate mode. (Thus the word is marked IMMEDIATE so it can - detect this and do different things). - - In immediate mode we just keep reading characters and printing them until we get to - the next double quote. - - In compile mode we have the problem of where we're going to store the string (remember - that the input buffer where the string comes from may be overwritten by the time we - come round to running the function). We store the string in the compiled function - like this: - ..., LITSTRING, string length, string rounded up to 4 bytes, EMITSTRING, ... -) -: .\" IMMEDIATE ( -- ) - STATE @ IF ( compiling? ) - ' LITSTRING , ( compile LITSTRING ) - HERE @ ( save the address of the length word on the stack ) - 0 , ( dummy length - we don't know what it is yet ) - BEGIN - KEY ( get next character of the string ) - DUP '\"' <> - WHILE - HERE @ !b ( store the character in the compiled image ) - 1 HERE +! ( increment HERE pointer by 1 byte ) - REPEAT - DROP ( drop the double quote character at the end ) - DUP ( get the saved address of the length word ) - HERE @ SWAP - ( calculate the length ) - 4- ( subtract 4 (because we measured from the start of the length word) ) - SWAP ! ( and back-fill the length location ) - HERE @ ( round up to next multiple of 4 bytes for the remaining code ) - 3 + - 3 INVERT AND - HERE ! - ' EMITSTRING , ( compile the final EMITSTRING ) - ELSE - ( In immediate mode, just read characters and print them until we get - to the ending double quote. Much simpler than the above code! ) - BEGIN - KEY - DUP '\"' = IF - DROP ( drop the double quote character ) - EXIT ( return from this function ) - THEN - EMIT - AGAIN - THEN -; - -( - In FORTH, global constants and variables are defined like this: - - 10 CONSTANT TEN when TEN is executed, it leaves the integer 10 on the stack - VARIABLE VAR when VAR is executed, it leaves the address of VAR on the stack - - Constants can be read by not written, eg: - - TEN . CR prints 10 - - You can read a variable (in this example called VAR) by doing: - - VAR @ leaves the value of VAR on the stack - VAR @ . CR prints the value of VAR - - and update the variable by doing: - - 20 VAR ! sets VAR to 20 - - Note that variables are uninitialised (but see VALUE later on which provides initialised - variables with a slightly simpler syntax). - - How can we define the words CONSTANT and VARIABLE? - - The trick is to define a new word for the variable itself (eg. if the variable was called - 'VAR' then we would define a new word called VAR). This is easy to do because we exposed - dictionary entry creation through the CREATE word (part of the definition of : above). - A call to CREATE TEN leaves the dictionary entry: - - +--- HERE - | - V - +---------+---+---+---+---+ - | LINK | 3 | T | E | N | - +---------+---+---+---+---+ - len - - For CONSTANT we can continue by appending DOCOL (the codeword), then LIT followed by - the constant itself and then EXIT, forming a little word definition that returns the - constant: - - +---------+---+---+---+---+------------+------------+------------+------------+ - | LINK | 3 | T | E | N | DOCOL | LIT | 10 | EXIT | - +---------+---+---+---+---+------------+------------+------------+------------+ - len codeword - - Notice that this word definition is exactly the same as you would have got if you had - written : TEN 10 ; -) -: CONSTANT - CREATE ( make the dictionary entry (the name follows CONSTANT) ) - DOCOL , ( append DOCOL (the codeword field of this word) ) - ' LIT , ( append the codeword LIT ) - , ( append the value on the top of the stack ) - ' EXIT , ( append the codeword EXIT ) -; - -( - VARIABLE is a little bit harder because we need somewhere to put the variable. There is - nothing particularly special about the 'user definitions area' (the area of memory pointed - to by HERE where we have previously just stored new word definitions). We can slice off - bits of this memory area to store anything we want, so one possible definition of - VARIABLE might create this: - - +--------------------------------------------------------------+ - | | - V | - +---------+---------+---+---+---+---+------------+------------+---|--------+------------+ - | | LINK | 3 | V | A | R | DOCOL | LIT | | EXIT | - +---------+---------+---+---+---+---+------------+------------+------------+------------+ - len codeword - - where is the place to store the variable, and points back to it. - - To make this more general let's define a couple of words which we can use to allocate - arbitrary memory from the user definitions area. - - First ALLOT, where n ALLOT allocates n bytes of memory. (Note when calling this that - it's a very good idea to make sure that n is a multiple of 4, or at least that next time - a word is compiled that n has been left as a multiple of 4). -) -: ALLOT ( n -- addr ) - HERE @ SWAP ( here n -- ) - HERE +! ( adds n to HERE, after this the old value of HERE is still on the stack ) -; - -( - Second, CELLS. In FORTH the phrase 'n CELLS ALLOT' means allocate n integers of whatever size - is the natural size for integers on this machine architecture. On this 32 bit machine therefore - CELLS just multiplies the top of stack by 4. -) -: CELLS 4 * ; - -( - So now we can define VARIABLE easily in much the same way as CONSTANT above. Refer to the - diagram above to see what the word that this creates will look like. -) -: VARIABLE - 1 CELLS ALLOT ( allocate 1 cell of memory, push the pointer to this memory ) - CREATE ( make the dictionary entry (the name follows VARIABLE) ) - DOCOL , ( append DOCOL (the codeword field of this word) ) - ' LIT , ( append the codeword LIT ) - , ( append the pointer to the new memory ) - ' EXIT , ( append the codeword EXIT ) -; - -( - VALUEs are like VARIABLEs but with a simpler syntax. You would generally use them when you - want a variable which is read often, and written infrequently. - - 20 VALUE VAL creates VAL with initial value 20 - VAL pushes the value directly on the stack - 30 TO VAL updates VAL, setting it to 30 - - Notice that 'VAL' on its own doesn't return the address of the value, but the value itself, - making values simpler and more obvious to use than variables (no indirection through '@'). - The price is a more complicated implementation, although despite the complexity there is no - particular performance penalty at runtime. - - A naive implementation of 'TO' would be quite slow, involving a dictionary search each time. - But because this is FORTH we have complete control of the compiler so we can compile TO more - efficiently, turning: - TO VAL - into: - LIT ! - and calculating (the address of the value) at compile time. - - Now this is the clever bit. We'll compile our value like this: - - +---------+---+---+---+---+------------+------------+------------+------------+ - | LINK | 3 | V | A | L | DOCOL | LIT | | EXIT | - +---------+---+---+---+---+------------+------------+------------+------------+ - len codeword - - where is the actual value itself. Note that when VAL executes, it will push the - value on the stack, which is what we want. - - But what will TO use for the address ? Why of course a pointer to that : - - code compiled - - - - --+------------+------------+------------+-- - - - - - by TO VAL | LIT | | ! | - - - - - --+------------+-----|------+------------+-- - - - - - | - V - +---------+---+---+---+---+------------+------------+------------+------------+ - | LINK | 3 | V | A | L | DOCOL | LIT | | EXIT | - +---------+---+---+---+---+------------+------------+------------+------------+ - len codeword - - In other words, this is a kind of self-modifying code. - - (Note to the people who want to modify this FORTH to add inlining: values defined this - way cannot be inlined). -) -: VALUE ( n -- ) - CREATE ( make the dictionary entry (the name follows VALUE) ) - DOCOL , ( append DOCOL ) - ' LIT , ( append the codeword LIT ) - , ( append the initial value ) - ' EXIT , ( append the codeword EXIT ) -; - -: TO IMMEDIATE ( n -- ) - WORD ( get the name of the value ) - FIND ( look it up in the dictionary ) - >DFA ( get a pointer to the first data field (the 'LIT') ) - 4+ ( increment to point at the value ) - STATE @ IF ( compiling? ) - ' LIT , ( compile LIT ) - , ( compile the address of the value ) - ' ! , ( compile ! ) - ELSE ( immediate mode ) - ! ( update it straightaway ) - THEN -; - -( - ID. takes an address of a dictionary entry and prints the word's name. - - For example: LATEST @ ID. would print the name of the last word that was defined. -) -: ID. - 4+ ( skip over the link pointer ) - .S CR - DUP @b ( get the flags/length byte ) - .S CR - F_LENMASK .S CR AND ( mask out the flags - just want the length ) - .S CR - - BEGIN - DUP 0> ( length > 0? ) - WHILE - SWAP 1+ ( addr len -- len addr+1 ) - DUP @b ( len addr -- len addr char | get the next character) - .\" print: \" DUP . CR - EMIT ( len addr char -- len addr | and print it) - SWAP 1- ( len addr -- addr len-1 | subtract one from length ) - REPEAT - DROP DROP ( len addr -- ) -; - -( - WORDS prints all the words defined in the dictionary, starting with the word defined most recently. - - The implementation simply iterates backwards from LATEST using the link pointers. -) - ( -: WORDS - LATEST @ ( start at LATEST dictionary entry ) - BEGIN - DUP @ 0<> ( while link pointer is not null ) - WHILE - - - - - - REPEAT - DROP -; ) - -( - So far we have only allocated words and memory. FORTH provides a rather primitive method - to deallocate. - - 'FORGET word' deletes the definition of 'word' from the dictionary and everything defined - after it, including any variables and other memory allocated after. - - The implementation is very simple - we look up the word (which returns the dictionary entry - address). Then we set HERE to point to that address, so in effect all future allocations - and definitions will overwrite memory starting at the word. We also need to set LATEST to - point to the previous word. - - Note that you cannot FORGET built-in words (well, you can try but it will probably cause - a segfault). - - XXX: Because we wrote VARIABLE to store the variable in memory allocated before the word, - in the current implementation VARIABLE FOO FORGET FOO will leak 1 cell of memory. -) -\\: FORGET - - - -( While compiling, [COMPILE] WORD compiles WORD if it would otherwise be IMMEDIATE. ) -: [COMPILE] IMMEDIATE - WORD ( get the next word ) - FIND ( find it in the dictionary ) - >CFA ( get its codeword ) - , ( and compile that ) -; - -( - RECURSE makes a recursive call to the current word that is being compiled. - - Normally while a word is being compiled, it is marked HIDDEN so that references to the - same word within are calls to the previous definition of the word. However we still have - access to the word which we are currently compiling through the LATEST pointer so we - can use that to compile a recursive call. -) -: RECURSE IMMEDIATE - LATEST @ >CFA ( LATEST points to the word being compiled at the moment ) - , ( compile it ) -; - -( Finally print the welcome prompt. ) -.\" JONESFORTH VERSION \" VERSION . CR -.\" OK \" -" - -_initbufftop: - .align 4096 -buffend: - -currkey: - .int buffer -bufftop: - .int _initbufftop - /* END OF jonesforth.S */