Clarify some explanations in the README
[rrq/jonasforth.git] / impl.asm
1 ;; vim: syntax=fasm
2
3 section '.text' code readable executable
4
5 macro printlen msg, len {
6   push rsi
7   add rsp, 8
8
9   mov rcx, msg
10   mov rdx, len
11   sys_print_string
12
13   sub rsp, 8
14   pop rsi
15 }
16
17 macro newline {
18   push $A
19   printlen rsp, 1
20 }
21
22 macro print msg {
23   printlen msg, msg#.len
24 }
25
26 struc string bytes {
27   . db bytes
28   .len = $ - .
29 }
30
31 ;; Find the given word in the dictionary of words. If no such word exists,
32 ;; return 0.
33 ;;
34 ;; Parameters:
35 ;;   * [find.search_length] = Length of the word in bytes.
36 ;;   * [find.search_buffer] = Pointer to the string containing the word.
37 ;;   * rsi = Pointer to the last entry in the dictionary.
38 ;;
39 ;; Results:
40 ;;   * rsi = Pointer to the found entry in the dictionary or 0.
41 ;;
42 ;; Clobbers rcx, rdx, rdi, rax.
43 find:
44   ;; RSI contains the entry we are currently looking at
45 .loop:
46   movzx rcx, byte [rsi + 8 + 1]    ; Length of word being looked at
47   cmp rcx, [.search_length]
48   jne .next    ; If the words don't have the same length, we have the wrong word
49
50   ;; Otherwise, we need to compare strings
51   lea rdx, [rsi + 8 + 1 + 1]    ; Location of character being compared in entry
52   mov rdi, [.search_buffer]     ; Location of character being compared in search buffer
53 .compare_char:
54   mov al, [rdx]
55   mov ah, [rdi]
56   cmp al, ah
57   jne .next                     ; They don't match; try again
58   inc rdx                       ; These characters match; look at the next ones
59   inc rdi
60   loop .compare_char
61
62   jmp .found                    ; They match! We are done.
63
64 .next:
65   mov rsi, [rsi]                ; Look at the previous entry
66   cmp rsi, 0
67   jnz .loop                    ; If there is no previous word, exit and return 0
68
69 .found:
70   ret
71
72 ;; Read a word from standard input. Returns pointer to string containing word as
73 ;; well as length.
74 ;;
75 ;; Results:
76 ;;   * rdx = Length of string
77 ;;   * rdi = Pointer to string buffer
78 ;;
79 ;; Clobbers pretty much everything.
80 read_word:
81 .skip_whitespace:
82   ;; Read characters into .char_buffer until one of them is not whitespace.
83   mov rsi, .char_buffer
84   sys_read_char
85
86   ;; We consider newlines and spaces to be whitespace.
87   cmp [.char_buffer], ' '
88   je .skip_whitespace
89   cmp [.char_buffer], $A
90   je .skip_whitespace
91
92 .alpha:
93   ;; We got a character that wasn't whitespace. Now read the actual word.
94   mov [.length], 0
95
96 .read_alpha:
97   mov al, [.char_buffer]
98   movzx rbx, [.length]
99   mov rsi, .buffer
100   add rsi, rbx
101   mov [rsi], al
102   inc [.length]
103
104   mov rsi, .char_buffer
105   sys_read_char
106
107   cmp [.char_buffer], ' '
108   je .end
109   cmp [.char_buffer], $A
110   jne .read_alpha
111
112 .end:
113   mov rdi, .buffer
114   movzx rdx, [.length]
115
116   ret
117
118 ;; Read a word from a buffer. Returns the buffer without the word, as well as
119 ;; the word that was read (including lengths).
120 ;;
121 ;; Inputs:
122 ;;   * rsi = Input buffer
123 ;;   * rcx = Length of buffer
124 ;;
125 ;; Outputs:
126 ;;   * rsi = Updated buffer
127 ;;   * rcx = Length of updated buffer
128 ;;   * rdi = Word buffer
129 ;;   * rdx = Length of word buffer
130 pop_word:
131 .skip_whitespace:
132   mov al, [rsi]
133   cmp al, ' '
134   je .got_whitespace
135   cmp al, $A
136   je .got_whitespace
137   jmp .alpha
138 .got_whitespace:
139   ;; The buffer starts with whitespace; discard the first character from the buffer.
140   inc rsi
141   dec rcx
142   jmp .skip_whitespace
143
144 .alpha:
145   ;; We got a character that wasn't whitespace. Now read the actual word.
146   mov rdi, rsi ; This is where the word starts
147   mov rdx, 1   ; Length of word
148
149 .read_alpha:
150   ;; Extract character from original buffer:
151   inc rsi
152   dec rcx
153
154   ;; When we hit whitespace, we are done with this word
155   mov al, [rsi]
156   cmp al, ' '
157   je .end
158   cmp al, $A
159   je .end
160
161   ;; It wasn't whitespace; add it to word buffer
162   inc rdx
163   jmp .read_alpha
164
165 .end:
166   ;; Finally, we want to skip one whitespace character after the word.
167   inc rsi
168   dec rcx
169
170   ret
171
172 ;; Parses a string.
173 ;;
174 ;; Parameters:
175 ;;   * rcx = Length of string
176 ;;   * rdi = Pointer to string buffer
177 ;;
178 ;; Results:
179 ;;   * rax = Value
180 ;;
181 ;; Clobbers
182 parse_number:
183   mov r8, 0                     ; Result
184
185   ;; Add (10^(rcx-1) * parse_char(rdi[length - rcx])) to the accumulated value
186   ;; for each rcx.
187   mov [.length], rcx
188 .loop:
189   ;; First, calcuate 10^(rcx - 1)
190   mov rax, 1
191
192   mov r9, rcx
193   .exp_loop:
194     dec r9
195     jz .break
196     mov rbx, 10
197     mul rbx
198     jmp .exp_loop
199   .break:
200
201   ;; Now, rax = 10^(rcx - 1).
202
203   ;; We need to calulate the value of the character at rdi[length - rcx].
204    mov rbx, rdi
205   add rbx, [.length]
206   sub rbx, rcx
207   movzx rbx, byte [rbx]
208   sub rbx, '0'
209
210   cmp rbx, 10
211   jae .error
212
213   ;; Multiply this value by rax to get (10^(rcx-1) * parse_char(rdi[length - rcx])),
214   ;; then add this to the result.
215   mul rbx
216
217   ;; Add that value to r8
218   add r8, rax
219
220   dec rcx
221   jnz .loop
222
223   mov rax, r8
224   ret
225
226 .error:
227   push rdi
228   print parse_number.error_msg
229   pop rdi
230   printlen rdi, [.length]
231   newline
232   sys_terminate 100
233
234 section '.data' readable writable
235
236 find.search_length dq ?
237 find.search_buffer dq ?
238
239 read_word.max_size = $FF
240 read_word.buffer rb read_word.max_size
241 read_word.length db ?
242 read_word.char_buffer db ?
243
244 parse_number.length dq ?
245 parse_number.error_msg string "Invalid number: "
246