Implement '!' and '@' commands and add 'STATE' variable
[rrq/jonasforth.git] / main.asm
index 834872cd54a756fef046f7a67c0dd1463fd8f86b..edcba0ae9421c712218041e64d4fc0872370f8d9 100644 (file)
--- a/main.asm
+++ b/main.asm
@@ -59,14 +59,20 @@ macro forth label, name {
   dq docol
 }
 
+
+
 segment readable executable
 
+entry main
+
+include "impl.asm"
+
 main:
   cld                        ; Clear direction flag so LODSQ does the right thing.
   mov rbp, return_stack_top  ; Initialize return stack
 
-  mov rsi, program
-  next
+  mov rax, MAIN
+  jmp qword [rax]
 
 program: dq MAIN
 
@@ -95,42 +101,31 @@ forth_asm LIT, 'LIT'
 ;; dictionary entry for that word. If no such word exists, return 0.
 forth_asm FIND, 'FIND'
   mov [.rsi], rsi
-  pop [.search_length]
-  pop [.search_buffer]
 
-  ;; RSI contains the entry we are currently looking at
+  pop [find.search_length]
+  pop [find.search_buffer]
   mov rsi, [latest_entry]       ; Start with the last added word
+  call find
+  push rsi
 
-.loop:
-  movzx rcx, byte [rsi + 8]     ; Length of word being looked at
-  cmp rcx, [.search_length]
-  jne .next    ; If the words don't have the same length, we have the wrong word
-
-  ;; Otherwise, we need to compare strings
-  lea rdx, [rsi + 8 + 1]        ; Location of character being compared in entry
-  mov rdi, [.search_buffer]     ; Location of character being compared in search buffer
-.compare_char:
-  mov al, [rdx]
-  mov ah, [rdi]
-  cmp al, ah
-  jne .next                     ; They don't match; try again
-  inc rdx                       ; These characters match; look at the next ones
-  inc rdi
-  loop .compare_char
-
-  jmp .found                    ; They match! We are done.
-
-.next:
-  mov rsi, [rsi]                ; Look at the previous entry
-  cmp rsi, 0
-  jnz .loop                    ; If there is no previous word, exit and return 0
-
-.found:
+  mov rsi, [.rsi]
+  next
   push rsi
 
   mov rsi, [.rsi]
   next
 
+;; Given an entry in the dictionary, return a pointer to the codeword of that
+;; entry.
+forth_asm TCFA, '>CFA'
+  pop rax
+  add rax, 8                    ; [rax] = length of name
+  movzx rbx, byte [rax]
+  inc rax
+  add rax, rbx                  ; [rax] = codeword
+  push rax
+  next
+
 ;; BRANCH is the fundamental mechanism for branching. BRANCH reads the next word
 ;; as a signed integer literal and jumps by that offset.
 forth_asm BRANCH, 'BRANCH'
@@ -149,6 +144,16 @@ forth_asm ZBRANCH, '0BRANCH'
   add rsi, 8     ; We need to skip over the next word, which contains the offset.
   next
 
+;; Duplicate the top of the stack.
+forth_asm DUP_, 'DUP'
+  push qword [rsp]
+  next
+
+;; Execute the codeword at the given address.
+forth_asm EXEC, 'EXEC'
+  pop rax
+  jmp qword [rax]
+
 ;; Expects a character on the stack and prints it to standard output.
 forth_asm EMIT, 'EMIT'
   pushr rsi
@@ -179,98 +184,25 @@ forth SPACE, 'SPACE'
 ;; size. The pointer is valid until the next call to READ_WORD.
 forth_asm READ_WORD, 'READ-WORD'
   mov [.rsi], rsi
-  mov [.rax], rax
-
-.skip_whitespace:
-  ;; Read characters into .char_buffer until one of them is not whitespace.
-  mov rax, 0
-  mov rdi, 0
-  mov rsi, .char_buffer
-  mov rdx, 1
-  syscall
 
-  cmp [.char_buffer], ' '
-  je .skip_whitespace
-  cmp [.char_buffer], $A
-  je .skip_whitespace
-
-.alpha:
-  ;; We got a character that wasn't whitespace. Now read the actual word.
-  mov [.length], 0
-
-.read_alpha:
-  mov al, [.char_buffer]
-  movzx rbx, [.length]
-  mov rsi, .buffer
-  add rsi, rbx
-  mov [rsi], al
-  inc [.length]
-
-  mov rax, 0
-  mov rdi, 0
-  mov rsi, .char_buffer
-  mov rdx, 1
-  syscall
-
-  cmp [.char_buffer], ' '
-  je .end
-  cmp [.char_buffer], $A
-  jne .read_alpha
-
-.end:
-  push .buffer
-  movzx rax, [.length]
-  push rax
+  call read_word
+  push rdi                      ; Buffer
+  push rdx                      ; Length
 
   mov rsi, [.rsi]
-  mov rax, [.rax]
-
   next
 
 ;; Takes a string on the stack and replaces it with the decimal number that the
 ;; string represents.
 forth_asm PARSE_NUMBER, 'PARSE-NUMBER'
-  pop [.length]                 ; Length
-  pop rdi                       ; String pointer
-  mov r8, 0                     ; Result
-
-  ;; Add (10^(rcx-1) * parse_char(rdi[length - rcx])) to the accumulated value
-  ;; for each rcx.
-  mov rcx, [.length]
-.loop:
-  ;; First, calcuate 10^(rcx - 1)
-  mov rax, 1
-
-  mov r9, rcx
-  .exp_loop:
-    dec r9
-    jz .break
-    mov rbx, 10
-    mul rbx
-    jmp .exp_loop
-  .break:
+  pop [parse_number.length]     ; Length
+  pop [parse_number.buffer]     ; String pointer
 
-  ;; Now, rax = 10^(rcx - 1).
-
-  ;; We need to calulate the value of the character at rdi[length - rcx].
-  mov rbx, rdi
-  add rbx, [.length]
-  sub rbx, rcx
-  movzx rbx, byte [rbx]
-  sub rbx, '0'
-
-  ;; Multiply this value by rax to get (10^(rcx-1) * parse_char(rdi[length - rcx])),
-  ;; then add this to the result.
-  mul rbx
-
-  ;; Add that value to r8
-  add r8, rax
-
-  dec rcx
-  jnz .loop
-
-  push r8
+  push rsi
+  call parse_number
+  pop rsi
 
+  push rax                      ; Result
   next
 
 forth READ_NUMBER, 'READ-NUMBER'
@@ -310,6 +242,15 @@ forth HELLO, 'HELLO'
   dq NEWLINE
   dq EXIT
 
+;; The INTERPRET word reads and interprets user input. It's behavior depends on
+;; the current STATE. It provides special handling for integers. (TODO)
+forth INTERPRET, 'INTERPRET'
+  dq READ_WORD
+  dq FIND
+  dq TCFA
+  dq EXEC
+  dq EXIT
+
 ;; .U prints the value on the stack as an unsigned integer in hexadecimal.
 forth_asm DOTU, '.U'
   mov [.length], 0
@@ -371,39 +312,44 @@ forth_asm DOTU, '.U'
   pop rsi
   next
 
+;; Takes a value and an address, and stores the value at the given address.
+forth_asm PUT, '!'
+  pop rbx                       ; Address
+  pop rax                       ; Value
+  mov [rbx], rax
+  next
+
+;; Takes an address and returns the value at the given address.
+forth_asm GET, '@'
+  pop rax
+  mov rax, [rax]
+  push rax
+  next
+
+;; Get the location of the STATE variable. It can be set with '!' and read with
+;; '@'.
+forth STATE, 'STATE'
+  dq LIT, var_STATE
+  dq EXIT
+
 forth MAIN, 'MAIN'
   dq HELLO
-  dq READ_WORD, FIND, DOTU, NEWLINE
-  dq BRANCH, -8 * 5
+  dq INTERPRET
+  dq BRANCH, -8 * 2
   dq TERMINATE
 
 segment readable writable
 
 latest_entry dq initial_latest_entry
 
-SPACE_string db 'SPACE'
-.length = $ - SPACE_string
-HELLO_string db 'HELLO'
-.length = $ - HELLO_string
-DOTU_string db '.U'
-.length = $ - DOTU_string
-HELLA_string db 'HELLA'
-.length = $ - HELLA_string
+;; The STATE variable is 0 when the interpreter is executing, and non-zero when
+;; it is compiling.
+var_STATE dq 0
 
-
-you_typed_string db 'You typed: '
-.length = $ - you_typed_string
-
-FIND.search_length dq ?
-FIND.search_buffer dq ?
 FIND.rsi dq ?
 
 READ_WORD.rsi dq ?
-READ_WORD.rax dq ?
-READ_WORD.max_size = $FF
-READ_WORD.buffer rb READ_WORD.max_size
-READ_WORD.length db ?
-READ_WORD.char_buffer db ?
+READ_WORD.rbp dq ?
 
 DOTU.chars db '0123456789ABCDEF'
 DOTU.buffer rq 16               ; 64-bit number has no more than 16 digits in hex
@@ -411,8 +357,6 @@ DOTU.rbuffer rq 16
 DOTU.length dq ?
 DOTU.printed_length dq ?
 
-PARSE_NUMBER.length dq ?
-
 ;; Return stack
 rq $2000
 return_stack_top: