summaryrefslogtreecommitdiff
path: root/stdio.asm
blob: 4069bff4bfbc268af6a5f38b8e494425b9592211 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
%ifndef _STDIO_ASM
%define _STDIO_ASM

%include "console.asm"

puts:
	; Write string buffer at cursor position
	push bp
	mov bp, sp
	pusha

	mov si, [bp + 4]	; address of string buffer
	mov bx, 0000h		;
	mov ah, 0eh		; BIOS - teletype

.loop:
	lodsb			; load byte at [si] into al
	or al, 0		; 0 | 0 = 0 (detect null terminator)
	je .end
	call putc		; write character
	jmp .loop
.end:
	popa
	mov sp, bp
	pop bp
	ret


printi:
	push bp
	mov bp, sp
	push ax
	mov ax, [bp + 4]
	call putint
.return:
	pop ax
	mov sp, bp
	pop bp
	ret


printh:
	push bp
	mov bp, sp
	push ax

	mov ax, [bp + 4]    ; integer WORD
	call puthex

.return:
	pop ax
	mov sp, bp
	pop bp
	ret


puthex:
	push ax
	push bx
	push cx
	push dx

	; ax is integer to print
	ror ah, 4		; reverse hex value
	ror al, 4
	xchg ah, al

	mov cx, 04h		; count (leading zeros)
	.divide:
		mov bx, 10h		; set divisor

		xor dx, dx		; clear mod
		div bx			; divide by 16

		cmp dl, 10		; don't adjust values less than 10
		jl .decimal
		.alpha:
			sub dl, 10		; (remainder - 10) -> align with ascii (base 10)
			add dl, 'A'		; (remainder + 'A') -> ascii offset conversion
			jmp .write
		.decimal:
			or dl, 30h 		; remainder -> ascii
	.write:
		dec cx
		xchg ax, dx	   	; exchange registers to get ascii value
		call putc		; print value
		xchg ax, dx		; restore registers

		cmp al, 0		; loop if al != 0
		jne .divide

		cmp cx, 0
		jne .divide

.return:
	pop dx
	pop cx
	pop bx
	pop ax
	ret


putint:
	push bp
	mov bp, sp
	pusha

	mov cx, 05h		; count (+leading zeros)
	mov di, 00h		; inner loop count
	.divide:
		mov bx, 0Ah		; set divisor

		xor dx, dx		; clear mod
		div bx			; divide by 10
		or dl, 30h 		; remainder -> ascii

		dec cx
		inc di			; local stack counter
		push dx

		cmp al, 0		; loop if al != 0
		jne .divide

		cmp cx, 0		; no more zeros?
		jne .divide

	.write:
		pop ax			; pop first value of integer
		dec di			; decrement our loop counter
		call putc		; write character
		cmp di, 0		; done?
		jne .write
.return:
	popa

	mov sp, bp
	pop bp
	ret


printf:
	push bp
	mov bp, sp

	mov di, bp		; save base pointer address
	push di

	mov bx, [bp + 4]	; format string address
	add bp, 6		; set base pointer to beginning of '...' args

	; count arguments
	std			; buffer direction 'up'
	mov cx, 0		; set counter
	mov si, bp		; source index is base pointer
	.count_args:
		lodsw		; load word at es:si
		add cx, 1	; increase arg count
		cmp ax, 0	; here we're looking for a "natural null terminator"
				; on the stack
		jne .count_args

	cld					; clear direction flag
	mov si, bx				; source index is format string
	.main_string:
		lodsb				; load byte in format string
						; BEGIN READING FORMAT STRING
		cmp al, '%'			; trigger parser on '%' symbol
		je .parse_fmt

		cmp al, 0			; if at end of format string
		je .finalize			; return

		call putc			; write character
						; when character is not a format specifier

		jmp .main_string		; repeat

		.parse_fmt:
			lodsb				; get next byte
			; switch(formatter)
							; [for example]
			cmp al, '%'			; '%%' - just print the character
			je .do_percent_escape

			cmp al, 'c'			; '%c' - proccess character
			je .do_char

			cmp al, 'd'			; '%d' - process integer
			je .do_int

			cmp al, 'x'			; '%x' - process hexadecimal
			je .do_hex

			cmp al, 's'			; '%s' - process string
			je .do_string

			jmp .do_default			; Matched nothing, so handle it

			; ---REMEMBER---
			; Our base pointer has been shifted
			; ---------------------------------
			; fmt  = bp + 4
			; arg1 = bp + 6 [<- we are here]
			; arg2 = bp + 8
			; arg3 = bp + 10
			; ...  = bp + ??

			.do_percent_escape:
				mov ax, '%'
				call putc
				jmp .main_string

			.do_char:
				mov ax, [bp]
				call putc
				jmp .main_string

			.do_hex:
				mov ax, [bp]
				call puthex
				jmp .parse_fmt_done

			.do_int:
				mov ax, [bp]
				call putint
				jmp .parse_fmt_done

			.do_string:
				mov ax, [bp]
				push ax
				call puts
				add sp, 2
				jmp .parse_fmt_done

			.do_default:
				; nothing found

	.parse_fmt_done:
		add bp, 2		; increment base pointer by one WORD
					; <<< these are our function arguments >>>
		jmp .main_string	; keep reading the format string

.finalize:
	mov bp, di			; restore original base pointer address.
					; this is pretty dangerous actually. if
					; a procedure modifies DI without restoring
					; it, we're doomed; we'll roll right off the
					; edge into oblivion.
	pop di
	mov sp, bp
	pop bp
	ret


%endif