1 /* A minimal FORTH interpreter for Linux / i386 systems. -*- asm -*-
2 * By Richard W.M. Jones <rich@annexia.org>
4 * gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
7 #include <asm-i386/unistd.h>
15 /* Macros to deal with the return stack. */
17 lea -4(%ebp),%ebp // push reg on to return stack
22 mov (%ebp),\reg // pop top of return stack to reg
26 /* ELF entry point. */
31 mov $return_stack,%ebp // Initialise the return stack.
33 mov $cold_start,%esi // Initialise interpreter.
34 NEXT // Run interpreter!
37 cold_start: // High-level code without a codeword.
40 /* DOCOL - the interpreter! */
44 PUSHRSP %esi // push %esi on to the return stack
45 addl $4,%eax // %eax points to codeword, so make
46 movl %eax,%esi // %esi point to first data word
49 /*----------------------------------------------------------------------
50 * Fixed sized buffers for everything.
54 /* FORTH return stack. */
55 #define RETURN_STACK_SIZE 8192
57 .space RETURN_STACK_SIZE
60 /* Space for user-defined words. */
61 #define USER_DEFS_SIZE 16384
71 /*----------------------------------------------------------------------
72 * Built-in words defined the long way.
77 // Store the chain of links.
80 .macro defcode name, namelen, flags=0, label
85 .byte \flags+\namelen // flags + length byte
86 .ascii "\name" // the name
92 .int code_\label // codeword
96 code_\label : // assembler code follows
99 .macro defword name, namelen, flags=0, label
104 .byte \flags+\namelen // flags + length byte
105 .ascii "\name" // the name
108 .set link,name_\label
111 .int DOCOL // codeword - the interpreter
112 // list of word pointers follow
115 /* COLD must not return (ie. must not call EXIT). */
116 defword "COLD",4,,COLD
117 .int LIT,'<',ECHO,WORD,ECHOWORD,LIT,'>',ECHO,LIT,10,ECHO,RDROP,COLD
119 defcode "EXIT",4,,EXIT
120 POPRSP %esi // pop return stack into %esi
124 // %esi points to the next command, but in this case it points to the next
125 // literal 32 bit integer. Get that literal into %eax and increment %esi.
126 // On x86, it's a convenient single byte instruction! (cf. NEXT macro)
128 push %eax // push %eax on to stack
132 pop %ebx // address to store at
133 pop %eax // data to store there
134 mov %eax,(%ebx) // store it
138 pop %ebx // address to fetch
139 mov (%ebx),%eax // fetch it
140 push %eax // push value onto stack
143 defcode "STATE",5,,STATE
147 defcode "HERE",4,,HERE
151 defcode "LATEST",6,,LATEST
156 pop %eax // pop parameter stack into %eax
157 PUSHRSP %eax // push it on to the return stack
160 defcode "R>",2,,FROMR
161 POPRSP %eax // pop return stack on to %eax
162 push %eax // and push on to parameter stack
165 #if 0 /* This definition is wrong. */
167 mov %(ebp),%eax // copy (don't pop) top of return stack to %eax
168 push %eax // and push on to parameter stack
172 defcode "RSP@",4,,RSPFETCH
176 defcode "RSP!",4,,RSPSTORE
180 defcode "RDROP",5,,RDROP
181 lea 4(%ebp),%ebp // pop return stack and throw away
186 push %eax // push return value on stack
198 mov $0,%ebx // out of input, exit (0)
202 defcode "WORD",4,,WORD
203 /* Search for first non-blank character. Also skip \ comments. */
205 call _KEY // get next key, returned in %eax
206 cmpb $'\\',%al // start of a comment?
207 je 3f // if so, skip the comment
209 jbe 1b // if so, keep looking
211 /* Search for the end of the word, storing chars as we go. */
212 mov $5f,%edi // pointer to return buffer
214 stosb // add character to return buffer
215 call _KEY // get next key, returned in %al
216 cmpb $' ',%al // is blank?
217 ja 2b // if not, keep looping
219 /* Return the word (well, the static buffer) and length. */
221 push %edi // push length
222 push $5f // push base address
225 /* Code to skip \ comments to end of the current line. */
228 cmpb $'\n',%al // end of line yet?
233 // A static buffer where WORD returns. Subsequent calls
234 // overwrite this buffer. Maximum word length is 32 chars.
237 defcode "ECHO",4,,ECHO
238 mov $1,%ebx // 1st param: stdout
240 // write needs the address of the byte to write
243 mov $2f,%ecx // 2nd param: address
245 mov $1,%edx // 3rd param: nbytes = 1
247 mov $__NR_write,%eax // write syscall
253 2: .space 1 // scratch used by ECHO
255 defcode "ECHOWORD",8,,ECHOWORD
256 mov $1,%ebx // 1st param: stdout
257 pop %ecx // 2nd param: address of string
258 pop %edx // 3rd param: length of string
260 mov $__NR_write,%eax // write syscall
266 pop %eax // duplicate top of stack
271 defcode "DROP",3,,DROP
272 pop %eax // drop top of stack
275 defcode "SWAP",4,,SWAP
276 pop %eax // swap top of stack
284 call nextword // get next word, the procedure name
285 // The next word is returned in %ebx and has length %ecx bytes.
287 // Save the current value of VOCAB.
291 // Change VOCAB to point to our new word's header (at LATEST).
295 // We'll start by writing the word's header at LATEST; the header
296 // is just length byte, the word itself, link pointer.
297 mov %ecx,(%edi) // Length byte
299 mov %ebx,%esi // Copy the string.
301 // Round up to the next multiple of 4 so that the link pointer
305 pop %eax // Link pointer, points to old VOCAB.
308 // Write the codeword, which for user-defined words is always a
309 // pointer to the FORTH indirect threaded interpreter.
313 // Finally, update LATEST. As we go along compiling, we'll be
314 // writing compiled codewords to the LATEST pointer (and moving
315 // it along each time).
318 movl $1,v_state // go into compiling mode
321 defcode ";",1,F_IMMED,SEMICOLON
326 defcode SYSEXIT,7,,SYSEXIT
331 /*----------------------------------------------------------------------
332 * Variables containing the interpreter's state.
338 .int 0 // 0 = immediate, non-zero = compiling
340 .int name_SYSEXIT // last word in the dictionary
342 .int user_defs_start // pointer to next space for user definition or current compiled def
344 /*----------------------------------------------------------------------
345 * Input buffer & initial input.
351 \\ Define some constants \n\
360 : CR '\\n' ECHO ; \n\