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