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>
9 /* NOTES----------------------------------------------------------------------
11 Need to say something about $ before constants.
13 And about je/jne/ja/jb/jbe/etc
26 /* Macros to deal with the return stack. */
28 lea -4(%ebp),%ebp // push reg on to return stack
33 mov (%ebp),\reg // pop top of return stack to reg
37 /* ELF entry point. */
42 mov $return_stack,%ebp // Initialise the return stack.
44 mov $cold_start,%esi // Initialise interpreter.
45 NEXT // Run interpreter!
48 cold_start: // High-level code without a codeword.
51 /* DOCOL - the interpreter! */
55 PUSHRSP %esi // push %esi on to the return stack
56 addl $4,%eax // %eax points to codeword, so make
57 movl %eax,%esi // %esi point to first data word
60 /*----------------------------------------------------------------------
61 * Fixed sized buffers for everything.
65 /* FORTH return stack. */
66 #define RETURN_STACK_SIZE 8192
68 .space RETURN_STACK_SIZE
71 /* Space for user-defined words. */
72 #define USER_DEFS_SIZE 16384
82 /*----------------------------------------------------------------------
83 * Built-in words defined the long way.
88 // Store the chain of links.
91 .macro defcode name, namelen, flags=0, label
98 .byte \flags+\namelen // flags + length byte
99 .ascii "\name" // the name
103 .int code_\label // codeword
107 code_\label : // assembler code follows
110 .macro defword name, namelen, flags=0, label
116 .set link,name_\label
117 .byte \flags+\namelen // flags + length byte
118 .ascii "\name" // the name
122 .int DOCOL // codeword - the interpreter
123 // list of word pointers follow
126 /* Some easy ones .... */
128 pop %eax // duplicate top of stack
133 defcode "DROP",3,,DROP
134 pop %eax // drop top of stack
137 defcode "SWAP",4,,SWAP
138 pop %eax // swap top of stack
144 /* COLD must not return (ie. must not call EXIT). */
145 defword "COLD",4,,COLD
146 .int LIT,'<',ECHO,WORD,ECHOWORD,LIT,'>',ECHO,LIT,10,ECHO,RDROP,COLD
149 This prints out each word in the input as <word>\n
150 defword "COLD",4,,COLD
151 .int LIT,'<',ECHO,WORD,ECHOWORD,LIT,'>',ECHO,LIT,10,ECHO,RDROP,COLD
154 defcode "EXIT",4,,EXIT
155 POPRSP %esi // pop return stack into %esi
159 // %esi points to the next command, but in this case it points to the next
160 // literal 32 bit integer. Get that literal into %eax and increment %esi.
161 // On x86, it's a convenient single byte instruction! (cf. NEXT macro)
163 push %eax // push %eax on to stack
167 pop %ebx // address to store at
168 pop %eax // data to store there
169 mov %eax,(%ebx) // store it
173 pop %ebx // address to fetch
174 mov (%ebx),%eax // fetch it
175 push %eax // push value onto stack
178 defcode "STATE",5,,STATE
182 defcode "HERE",4,,HERE
186 defcode "LATEST",6,,LATEST
191 pop %eax // pop parameter stack into %eax
192 PUSHRSP %eax // push it on to the return stack
195 defcode "R>",2,,FROMR
196 POPRSP %eax // pop return stack on to %eax
197 push %eax // and push on to parameter stack
200 #if 0 /* This definition is wrong. */
202 mov %(ebp),%eax // copy (don't pop) top of return stack to %eax
203 push %eax // and push on to parameter stack
207 defcode "RSP@",4,,RSPFETCH
211 defcode "RSP!",4,,RSPSTORE
215 defcode "RDROP",5,,RDROP
216 lea 4(%ebp),%ebp // pop return stack and throw away
221 push %eax // push return value on stack
233 mov $0,%ebx // out of input, exit (0)
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 "WORD",4,,WORD
257 push %eax // push length
258 push %ebx // push base address
262 /* Search for first non-blank character. Also skip \ comments. */
264 call _KEY // get next key, returned in %eax
265 cmpb $'\\',%al // start of a comment?
266 je 3f // if so, skip the comment
268 jbe 1b // if so, keep looking
270 /* Search for the end of the word, storing chars as we go. */
271 mov $5f,%edi // pointer to return buffer
273 stosb // add character to return buffer
274 call _KEY // get next key, returned in %al
275 cmpb $' ',%al // is blank?
276 ja 2b // if not, keep looping
278 /* Return the word (well, the static buffer) and length. */
280 mov %edi,%eax // return length of the word
281 mov $5f,%ebx // return address of the word
284 /* Code to skip \ comments to end of the current line. */
287 cmpb $'\n',%al // end of line yet?
292 // A static buffer where WORD returns. Subsequent calls
293 // overwrite this buffer. Maximum word length is 32 chars.
296 defcode "ECHOWORD",8,,ECHOWORD
297 mov $1,%ebx // 1st param: stdout
298 pop %ecx // 2nd param: address of string
299 pop %edx // 3rd param: length of string
301 mov $__NR_write,%eax // write syscall
307 call _WORD // returns %ebx = address of next word, %eax = length in bytes
308 mov %ebx,%edi // %edi = address
309 mov %eax,%ecx // %ecx = length
311 push %esi // Save %esi so we can use it in string comparison.
313 // Now we start searching backwards through the dictionary for this word.
314 mov v_latest,%edx // LATEST points to name header of the latest word in the dictionary
316 cmp %edx,%edx // NULL pointer? (end of the linked list)
320 movb 4(%edx),%al // %al = flags+length field
321 andb $0x1f,%al // %al = name length
322 cmpb %cl,%al // Length is the same?
325 // Compare the strings in detail.
326 push %ecx // Save the length
327 lea 5(%edx),%esi // Dictionary string we are checking against.
328 repe cmpsb // Compare the strings.
330 jne 2f // Not the same.
332 // The strings are the same - return the header pointer on the stack.
338 mov (%edx),%edx // Move back through the link field to the previous word
339 jmp 1b // .. and loop.
343 xor %eax,%eax // Push zero on to the stack to indicate not found.
349 call nextword // get next word, the procedure name
350 // The next word is returned in %ebx and has length %ecx bytes.
352 // Save the current value of VOCAB.
356 // Change VOCAB to point to our new word's header (at LATEST).
360 // We'll start by writing the word's header at LATEST; the header
361 // is just length byte, the word itself, link pointer.
362 mov %ecx,(%edi) // Length byte
364 mov %ebx,%esi // Copy the string.
366 // Round up to the next multiple of 4 so that the link pointer
370 pop %eax // Link pointer, points to old VOCAB.
373 // Write the codeword, which for user-defined words is always a
374 // pointer to the FORTH indirect threaded interpreter.
378 // Finally, update LATEST. As we go along compiling, we'll be
379 // writing compiled codewords to the LATEST pointer (and moving
380 // it along each time).
383 movl $1,v_state // go into compiling mode
386 defcode ";",1,F_IMMED,SEMICOLON
391 defcode SYSEXIT,7,,SYSEXIT
396 /*----------------------------------------------------------------------
397 * Variables containing the interpreter's state.
403 .int 0 // 0 = immediate, non-zero = compiling
405 .int name_SYSEXIT // last word in the dictionary
407 .int user_defs_start // pointer to next space for user definition or current compiled def
409 /*----------------------------------------------------------------------
410 * Input buffer & initial input.
416 \\ Define some constants \n\
425 : CR '\\n' ECHO ; \n\