1 format ELF64 executable
3 ;; The code in this macro is placed at the end of each Forth word. When we are
4 ;; executing a definition, this code is what causes execution to resume at the
5 ;; next word in that definition.
7 ;; RSI points to the address of the definition of the next word to execute.
8 lodsq ; Load value at RSI into RAX and increment RSI
9 ;; Now RAX contains the location of the next word to execute. The first 8
10 ;; bytes of this word is the address of the codeword, which is what we want
12 jmp qword [rax] ; Jump to the codeword of the current word
15 ;; pushr and popr work on the return stack, whose location is stored in the
26 segment readable executable
29 cld ; Clear direction flag so LODSQ does the right thing.
30 mov rbp, return_stack_top ; Initialize return stack
37 ;; The codeword is the code that will be executed at the beginning of a forth
38 ;; word. It needs to save the old RSI and update it to point to the next word to
41 pushr rsi ; Save old value of RSI on return stack; we will continue execution there after we are done executing this word
42 lea rsi, [rax + 8] ; RAX currently points to the address of the codeword, so we want to continue at RAX+8
43 next ; Execute word pointed to by RSI
45 ;; This word is called at the end of a Forth definition. It just needs to
46 ;; restore the old value of RSI (saved by 'docol') and resume execution.
57 ;; LIT is a special word that reads the next "word pointer" and causes it to be
58 ;; placed on the stack rather than executed.
70 ;; Given a string (a pointer following by a size), return the location of the
71 ;; dictionary entry for that word. If no such word exists, return 0.
83 ;; RSI contains the entry we are currently looking at
84 mov rsi, [latest_entry] ; Start with the last added word
87 movzx rcx, byte [rsi + 8] ; Length of word being looked at
88 cmp rcx, [.search_length]
89 jne .next ; If the words don't have the same length, we have the wrong word
91 ;; Otherwise, we need to compare strings
92 lea rdx, [rsi + 8 + 1] ; Location of character being compared in entry
93 mov rdi, [.search_buffer] ; Location of character being compared in search buffer
98 jne .next ; They don't match; try again
99 inc rdx ; These characters match; look at the next ones
103 jmp .found ; They match! We are done.
106 mov rsi, [rsi] ; Look at the previous entry
108 jnz .loop ; If there is no previous word, exit and return 0
116 ;; BRANCH is the fundamental mechanism for branching. BRANCH reads the next word
117 ;; as a signed integer literal and jumps by that offset.
125 add rsi, [rsi] ; [RSI], which is the next word, contains the offset; we add this to the instruction pointer.
126 next ; Then, we can just continue execution as normal
128 ;; 0BRANCH is like BRANCH, but it jumps only if the top of the stack is zero.
132 ;; Compare top of stack to see if we should branch
139 add rsi, 8 ; We need to skip over the next word, which contains the offset.
142 ;; Expects a character on the stack and prints it to standard output.
158 ;; Prints a newline to standard output.
165 ;; Prints a space to standard output.
176 ;; Read a word from standard input and push it onto the stack as a pointer and a
177 ;; size. The pointer is valid until the next call to READ_WORD.
185 ;; Read characters into .char_buffer until one of them is not whitespace.
188 mov rsi, .char_buffer
192 cmp [.char_buffer], ' '
194 cmp [.char_buffer], $A
198 ;; We got a character that wasn't whitespace. Now read the actual word.
202 mov al, [.char_buffer]
211 mov rsi, .char_buffer
215 cmp [.char_buffer], ' '
217 cmp [.char_buffer], $A
230 ;; Takes a string on the stack and replaces it with the decimal number that the
231 ;; string represents.
235 pop [.length] ; Length
236 pop rdi ; String pointer
239 ;; Add (10^(rcx-1) * parse_char(rdi[length - rcx])) to the accumulated value
243 ;; First, calcuate 10^(rcx - 1)
255 ;; Now, rax = 10^(rcx - 1).
257 ;; We need to calulate the value of the character at rdi[length - rcx].
261 movzx rbx, byte [rbx]
264 ;; Multiply this value by rax to get (10^(rcx-1) * parse_char(rdi[length - rcx])),
265 ;; then add this to the result.
268 ;; Add that value to r8
284 ;; Takes a string (in the form of a pointer and a length on the stack) and
285 ;; prints it to standard output.
302 ;; Exit the program cleanly.
323 push you_typed_string
324 push you_typed_string.length
342 ;; .U prints the value on the stack as an unsigned integer in hexadecimal.
351 mov [.printed_length], 1
352 pop rax ; RAX = value to print
353 push rsi ; Save value of RSI
355 ;; We start by constructing the buffer to print in reverse
360 div rbx ; Put remainer in RDX and quotient in RAX
362 ;; Place the appropriate character in the buffer
371 ;; .printed_length is the number of characters that we ulitmately want to
372 ;; print. If we have printed a non-zero character, then we should update
375 je .skip_updating_real_length
377 mov [.printed_length], rbx
378 .skip_updating_real_length:
383 ;; Flip buffer around, since it is currently reversed
384 mov rcx, [.printed_length]
392 add rdi, [.printed_length]
402 mov rdx, [.printed_length]
405 ;; Restore RSI and continue execution
412 dq LIT, SPACE_entry, DOTU, NEWLINE
413 dq LIT, HELLO_entry, DOTU, NEWLINE
414 dq LIT, DOTU_entry, DOTU, NEWLINE
415 dq LIT, SPACE_string, LIT, SPACE_string.length, TELL, SPACE
416 dq LIT, SPACE_string, LIT, SPACE_string.length, FIND, DOTU, NEWLINE
417 dq LIT, HELLO_string, LIT, HELLO_string.length, TELL, SPACE
418 dq LIT, HELLO_string, LIT, HELLO_string.length, FIND, DOTU, NEWLINE
419 dq LIT, DOTU_string, LIT, DOTU_string.length, TELL, SPACE
420 dq LIT, DOTU_string, LIT, DOTU_string.length, FIND, DOTU, NEWLINE
421 dq LIT, HELLA_string, LIT, HELLA_string.length, TELL, SPACE
422 dq LIT, HELLA_string, LIT, HELLA_string.length, FIND, DOTU, NEWLINE
425 segment readable writable
427 latest_entry dq DOTU_entry
429 SPACE_string db 'SPACE'
430 .length = $ - SPACE_string
431 HELLO_string db 'HELLO'
432 .length = $ - HELLO_string
434 .length = $ - DOTU_string
435 HELLA_string db 'HELLA'
436 .length = $ - HELLA_string
439 you_typed_string db 'You typed: '
440 .length = $ - you_typed_string
442 FIND.search_length dq ?
443 FIND.search_buffer dq ?
448 READ_WORD.max_size = $FF
449 READ_WORD.buffer rb READ_WORD.max_size
450 READ_WORD.length db ?
451 READ_WORD.char_buffer db ?
453 DOTU.chars db '0123456789ABCDEF'
454 DOTU.buffer rq 16 ; 64-bit number has no more than 16 digits in hex
457 DOTU.printed_length dq ?
459 PARSE_NUMBER.length dq ?