commit ad1ee3243fe6a83760842cbf5c6b13fcb253cc0e
parent ef492b4f00282956ab266259992fa2bf86d1447f
Author: Christian Ermann <christianermann@gmail.com>
Date:   Thu, 14 Dec 2023 21:13:58 -0500
Add KEY, WORD, DOT, and U_DOT
Diffstat:
| M | efi.asm | | | 37 | ++++++++++++++++++++++++------------- | 
| M | forth.asm | | | 203 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- | 
| M | run.sh | | | 1 | - | 
3 files changed, 219 insertions(+), 22 deletions(-)
diff --git a/efi.asm b/efi.asm
@@ -123,6 +123,18 @@ struc INPUT_KEY {
 }
 struct INPUT_KEY
 
+macro preserve_registers {
+    push rbx
+    push rcx
+    push rdx
+}
+
+macro restore_registers {
+    pop rdx
+    pop rcx
+    pop rbx
+}
+
 sys_initialize:
 ; Store the address of the EFI system table in the `system_table` variable.
 ;
@@ -132,7 +144,9 @@ sys_initialize:
 ; `system_table` is defined as an 8-byte variable in the data section at the
 ; bottom of this file.
 ;
+    preserve_registers
     mov [system_table], rdx
+    restore_registers
     ret
 
 sys_clear_screen:
@@ -140,12 +154,14 @@ sys_clear_screen:
 ;
 ; This function will clobber the RCX and RBX registers.
 ;
+    preserve_registers
     mov rcx, [system_table]
     mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
     mov rbx, [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.ClearScreen]
     sub rsp, 32
     call rbx
     add rsp, 32
+    restore_registers
     ret
 
 sys_print_string:
@@ -155,13 +171,14 @@ sys_print_string:
 ; null-terminated version of the input string. 1024 bytes (512 characters) are
 ; reserved for this buffer in the data section at the bottom of this file.
 ;
-; This function will clobber the RDI, RAX, RBX, RCX, and RDX registers.
+; This function will clobber the RDI and RAX registers.
 ;
 ; Args:
 ;    RCX: The address of an ASCII string. The string does not need to be
 ;         null-terminated.
 ;    RDX: The length of the string.
 ;
+    preserve_registers
     mov rdi, .output_buffer
 
 .copy_char:
@@ -222,6 +239,7 @@ sys_print_string:
     sub rsp, 32
     call rbx
     add rsp, 32
+    restore_registers
     ret
 
 sys_read_char:
@@ -230,6 +248,7 @@ sys_read_char:
 ; The EFI ReadKeyStroke function actually returns a UTF-16 character but I only
 ; want to deal with ASCII characters for now.
 ;
+    preserve_registers
 
 .read_key:
 ; Call the EFI ReadKeyStroke function until a key is pressed.
@@ -258,21 +277,13 @@ sys_read_char:
 ;
     movzx rax, word [.input_key.UnicodeChar]
     cmp ax, 0xD
-    jne .no_enter
+    jne .end
     mov rax, 0xA
 
-.no_enter:
-; Echo the character onto stdout and return.
-;
-; The echo will likely be removed in the future but is here currently for
-; debugging purposes.
+.end:
+; Restore registers saved at the start of the function
 ;
-    push rax
-    mov [.char_buffer], al
-    mov rcx, .char_buffer
-    mov rdx, 1
-    call sys_print_string
-    pop rax
+    restore_registers
     ret
 
 section '.data' data readable writeable
diff --git a/forth.asm b/forth.asm
@@ -28,6 +28,8 @@ include 'efi.asm'
 ; 1. The Basic Structure of Forth
 ; 2. Executing this Program
 ; 3. Hello, World!
+; 4. Reading Words
+; 5. Printing Numbers
 ;
 ;------------------------------------------------------------------------------
 ;
@@ -171,7 +173,7 @@ macro defcode name, name_length, flags, label_
 ; Define a primitive word.
 ;
 {
-    section '.data' data readable
+section '.rodata' data readable
     align 8
 name_#label_:
     dq link               ; 1. Set the link pointer.
@@ -183,7 +185,7 @@ name_#label_:
     align 8               ; 6. Add any padding we may need.
 label_:
     dq code_#label_       ; 7. Set the codeword.
-    section '.text' code readable executable
+section '.text' code readable executable
 
     align 1
 code_#label_:             ; 8. This is where our assembly code will go.
@@ -240,14 +242,21 @@ defcode "EXIT", 4, 0, EXIT
 ; We'll use the variable `program` to store the address of the w
 ;
 
+postpone {
+section '.text' code executable readable
 main:
     cld
     call sys_initialize
     call sys_clear_screen
-    push version_string
-    push version_string.length
+    initialize_stack
     mov rsi, program
     NEXT
+}
+
+macro set_program init_stack_macro, program_label {
+    macro initialize_stack \{ init_stack_macro \}
+    program equ program_label
+}
 
 ;------------------------------------------------------------------------------
 ;
@@ -291,16 +300,194 @@ defcode "TYPE", 4, 0, TYPE
     call sys_print_string
     NEXT
 
-section '.rodata' readable
+section '.rodata' data readable
+
+macro initialize_stack_s3 {
+    push version_string
+    push version_string.length
+}
+
+program_s3:
+    dq TYPE
+
+set_program initialize_stack_s3, program_s3
+
+;------------------------------------------------------------------------------
+;
+; Section 4 - Reading Words
+;
+
+defcode "KEY", 3, 0, KEY
+    call sys_read_char
+    push rax
+    NEXT
+
+defcode "WORD", 4, 0, WORD_
+
+.skip_whitespace_and_comments:
+    call sys_read_char
+    cmp al, 0x20
+    je .skip_whitespace_and_comments
+    cmp al, 0xA
+    je .skip_whitespace_and_comments
+    cmp al, 0xD
+    je .skip_whitespace_and_comments
+
+    mov rdx, .buffer
+
+.store_char:
+    mov byte [rdx], al
+    inc rdx
+
+.next_char:
+    call sys_read_char
+    cmp al, 0x20
+    je .end
+    cmp al, 0xA
+    jne .store_char
+
+.end:
+    mov rcx, .buffer       
+    sub rdx, rcx
+    push rcx
+    push rdx
+    NEXT
+
+macro initialize_stack_s4 {
+    push version_string
+    push version_string.length
+}
+
+program_s4:
+    dq TYPE
+    dq WORD_
+    dq TYPE
+
+set_program initialize_stack_s4, program_s4
+
+;------------------------------------------------------------------------------
+;
+; Section 5 - Printing Numbers
+;
+
+defcode ".", 1, 0, DOT
+; Print a signed integer.
+;
+    pop rax
+    movzx rcx, [BASE]
+    xor rbx, rbx
+    xor r8, r8
+
+.check_negative:
+    test rax, rax
+    jns .divide
+    neg rax
+    inc r8
+
+.divide:
+    xor rdx, rdx
+    div rcx
+    push rdx
+    inc rbx
+    test rax, rax
+    jnz .divide
+
+.reverse_digits:
+    mov rcx, rbx
+    mov rdx, .buffer
+
+.handle_negative:
+    test r8, r8
+    jz .next_digit
+    mov byte [rdx], 0x2D
+    inc rdx
+    inc rbx
+
+.next_digit:
+    pop rax
+    add al, 48
+    cmp al, 58
+    jl .store_in_buffer
+    add al, 8
+
+.store_in_buffer:
+    mov [rdx], al
+    inc rdx
+    dec rcx
+    jnz .next_digit
+
+.end:
+    mov byte [rdx], 0xA
+    inc rbx
+    mov rcx, .buffer
+    mov rdx, rbx
+    call sys_print_string
+    NEXT
+
+
+defcode "U.", 2, 0, U_DOT
+; Print an unsigned integer.
+;
+    pop rax
+    movzx rcx, [BASE]
+    xor rbx, rbx
+
+.divide:
+    xor rdx, rdx
+    div rcx
+    push rdx
+    inc rbx
+    test rax, rax
+    jnz .divide
+
+.reverse_digits:
+    mov rcx, rbx
+    mov rdx, .buffer
+
+.next_digit:
+    pop rax
+    add al, 48
+    cmp al, 58
+    jl .store_in_buffer
+    add al, 7
+
+.store_in_buffer:
+    mov [rdx], al
+    inc rdx
+    dec rcx
+    jnz .next_digit
 
-program_s1:
+.end:
+    mov byte [rdx], 0xA
+    inc rbx
+    mov rcx, .buffer
+    mov rdx, rbx
+    call sys_print_string
+    ret
+
+macro initialize_stack_s5 {
+    push 0xBADC0DE
+    push -0xBAD
+    push version_string
+    push version_string.length
+}
+
+program_s5:
     dq TYPE
+    dq DOT
+    dq U_DOT
 
-program dq program_s1
+;set_program initialize_stack_s5, program_s5
 
-section '.data' readable writable
+section '.data' data readable writable
 
 version_string db 'soup forth v0.1', 0xA
 .length = $ - version_string
 
 code_EMIT.char_buffer db ?
+
+code_DOT.buffer rb 64
+code_U_DOT.buffer rb 64
+code_WORD_.buffer rb 64
+
+BASE db 16
diff --git a/run.sh b/run.sh
@@ -6,4 +6,3 @@ exec qemu-system-x86_64 \
          -net none \
          -bios /usr/share/ovmf/OVMF.fd \
          -drive file=boot.img,format=raw
-