-
Notifications
You must be signed in to change notification settings - Fork 1
/
6.asm
363 lines (314 loc) · 11.6 KB
/
6.asm
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
.486
assume cs:code, ds:data, ss:stack
; Macros
print_text_on_cursor MACRO text ; text must end with '$'
push ax
push dx
mov ah, 09h ; display a text string (must end with an ASCII $ (24H))
mov dx, offset text ; offset of text from segment (where data is located),
; dx's value is the address of the text string
int 21h
pop dx
pop ax
ENDM
print_text_at_pos MACRO text, pos ; text must end with '0'
LOCAL l_pt
push si
push di
lea si, text
mov di, pos
l_pt:
movsb ; move string byte/data, es:di <- ds:si, di <- di+1, si <- si+1
inc di
cmp byte ptr[si], eom
jne l_pt
pop di
pop si
ENDM
printHex2Ascii MACRO hex, digits_num
LOCAL mulbx
LOCAL setcxbybx
LOCAL print_loop
LOCAL l_display_digit
LOCAL l_leading_zero_false
LOCAL end_macro
push bx
mov ax, hex
mov dx, 0
mov bx, 1 ; initial value of bx
mov cx, digits_num
dec cx ; cx is decreased by 1, to produce the right divisor
; (using the loop 'mulbx').
cmp cx, 0 ; if cx = 0, then
je setcxbybx ; don't multiply bx (but just set cx equals to bx), else
; multiply bx by 10 each time (times equal to cx)
mulbx:
imul bx, 10
loop mulbx
setcxbybx:
mov cx, bx ; CX now contains the divisor for the first
; division with dx-ax (the divident) below.
mov leading_zero, 1 ; If the first digit is a zero, it's considered as a
; leading zero.
print_loop:
mov dx, 0
; We don't care about the quotient (which after the xchg is now on dx),
; and also it prevents problems with the division (div cx),
; because otherwise if dx is not 0, the dividend (dx-ax) would become
; a quite large and unwanted/wrong number, and the program will crash
; (especially when the divisor (cx) equals 1, the quotient (ax) would be
; equal to the dividend (dx-ax) and as it's obvious ax (16 bit) cannot
; store dx-ax (32 bit)).
div cx ; dx-ax / cx => dx = remainder, ax = quotient
push ax
push ax ; ax is pushed twice, because the called subroutine below,
; when it returns, discards (with 'ret 2') what was just pushed
; onto the stack before it was called (actually, 'ret 2' increases
; the stack pointer by 2)
call hex2ascii ; 'call' first pushes the current address onto the stack,
; then does an unconditional jump to the specified label
; (i.e. the name of the subroutine)
pop ax
; ---- trim leading zeros (trim e.g. 0009 to 9, 0125 to 125 etc) ----
push ax
mov ah, buffer[3] ; Because we have defined buffer to contain elements
; of 1 byte, we can't copy an element from buffer
; to ax (which the latter is 2 bytes).
; Therefore, we have to use ah (which is 1 byte) and
; complies with the size of buffer's elements.
cmp ah, '0' ; Check if digit is 0, and
jne l_leading_zero_false ; if not, then digit is 1-9 and jump to
; set the flag leading_zero to 0 and
; then just display the digit.
cmp leading_zero, 1 ; (Else, digit is 0 and) check if it's a
; leading zero, and
jne l_display_digit ; if not, then jump to just display it.
cmp cx, 1 ; (Else, digit is 0 and) check if it's the
; last digit, and
je l_display_digit ; if it's, then jump to just display it.
; Else, digit is 0 and it's a leading zero, so:
mov buffer[3], dollar ; We convert digit 0 to dollar ($) sign,
; because with just that character the macro
; 'print_text_on_cursor' will display nothing.
jmp l_display_digit ; Jump to execute that macro.
l_leading_zero_false:
mov leading_zero, 0 ; Zeroes that aren't leading, are going to be displayed.
l_display_digit:
print_text_on_cursor buffer[3]
; pseudocode:
; if (digit != 0) then
; set leading_zero equal to 0/false
; display the digit
; else if (digit == 0 AND leading_zero == 0/false) then
; display the digit
; else ; (digit == 0 AND leading_zero == 1/true)
; convert the digit 0 to '$'
; display the digit
; end
pop ax
; ---- trimming ends here ----
xchg dx, ax
; check condition:
cmp cx, 1 ; when cx is 1, there is no meaning to perform the divisions
; anymore, thus the macro is considered finished
je end_macro
; divide cx by 10:
push ax
push dx
mov dx, 0
mov ax, cx
mov cx, 10
div cx
mov cx, ax
pop dx
pop ax
; ----
jmp print_loop
end_macro:
pop bx
ENDM
; Constants
dollar equ '$'
neg_sign equ 242dh ; 24 is the dollar ($) sign, 2d is the hyphen (-) character.
; They're placed according to the little-endian format, in which
; Intel x86 assembly is based on. This is used to display the negative sign.
; The dollar sign is included, because the macro 'print_text_on_cursor'
; terminates the displaying of text when it finds the dollar sign.
; Segments
stack segment use16 para stack
db 256 dup(' ')
stack ends
data segment use16
array dw 9, 198, -108, 6, -125, 40, -9, 202, 1
elements_num equ (($-array)/2) ; $-array generates the size of
; the array in bytes
; (($-array)/2) calculates the number of elements:
; the length of the array is divided by 2, because each
; element takes 2 bytes in memory, and thus that calculation
; returns the number of elements.
msg_unsort db 'Unsorted list:'
db dollar
msg_bsort db 'Bubble-sorted list:'
db dollar
new_line db 10, 13 ; 10=CR:Carriage Return, 13=LF:Line Feed (Windows endings)
db dollar
swap_count db 1
sign dw dollar
leading_zero db 1
buffer db 4 dup(0)
db dollar
data ends
code segment use16 para public 'code'
start:
main proc far
; set up stack for return
push ds
mov ax, 0
push ax
; ----
; set DS register to data segment
mov ax, data
mov ds, ax
; display the unsorted list
print_text_on_cursor new_line
print_text_on_cursor msg_unsort
print_text_on_cursor new_line
call display_array
print_text_on_cursor new_line
; ---- actual sorting starts here ----
next_pass:
mov al, swap_count ; checks to see how many swaps have been made
; in the current pass
cmp al, 0 ; if no swaps, then the array is sorted,
je done ; and thus jump to 'done'
mov swap_count, 0 ; if the array is no sorted yet, then reset swap_count to 0
mov di, 0 ; Index to array's element, starting from the first (position 0),
; also counter of how how many elements have been checked.
next_elem:
shl di, 1 ; di=di*2
mov ax, array[di]
cmp ax, array[di+2] ; compare the first number with the next one
; (each element/number takes 2 bytes in memory)
jg swap ; if the next one is greater than the first, then jump to the
; label 'swap', where the swapping of the two numbers takes place,
; the two numbers exchange places in the array
increment:
shr di, 1 ; di=di/2, it neutralizes the effect from the command
; 'shl di, 1', found above (below 'next_elem')
inc di
mov al, elements_num
and ax, 00ffh ; mask: keeps only the low byte of ax (i.e. only al), and
; sets the high byte (i.e. ah) to 0
cmp di, ax ; checks if all elements have been checked
; (actually, the maximum value di can have is elements_num-1),
je next_pass ; if so, jump to 'next_pass'
jmp next_elem ; else, jump to 'next_elem'
swap:
mov bx, array[di+2]
mov array[di+2], ax ; ax already has the value of 'array[di]' from 'next_elem'
mov array[di], bx
inc swap_count
jmp increment
; ---- end of sorting ----
done:
; display the bubble-sorted list
print_text_on_cursor msg_bsort
print_text_on_cursor new_line
call display_array
print_text_on_cursor new_line
ret ; return to DOS
main endp
; Subroutines
display_array proc near
push di
mov di, 0
l_next_elem:
push di
shl di, 1 ; di=di*2
mov ax, array[di]
mov bx, ax ; The element is copied to bx, as the parameter of the macro
; below, and also because we modify ax later (and thus ax
; can't be used as a parameter of that macro anymore).
; check if it's a negative number:
and ax, 0ff00h ; keep only the high byte
cmp ah, 0ffh ; By checking if the high byte is equal to ffh, it can
; be determined if it's a negative number or not (and
; that's because all negative numbers' high byte is equal
; to ffh).
je l_negative_number
mov sign, dollar ; If it isn't a negative number, the sign is just blank,
; (we tend to ommit the + sign if it's positive).
l_display:
; display the sign and the number
print_text_on_cursor sign ; Display the sign (just before the number),
; (in case it's positive, display nothing).
printHex2Ascii bx, 3 ; convert the absolute (hex) number to ascii
; We set 3 as the number of digits to display,
; because the maximum (absolute) number it can
; display it's constituted of 3 digits (-128 or 127).
print_text_on_cursor new_line
pop di
inc di
mov al, elements_num
and ax, 0fh ; mask
cmp di, ax
jb l_next_elem
pop di
ret ; return to main procedure
l_negative_number:
mov sign, neg_sign ; set sign as the negative (-) sign
; calculate the absolute value of the negative number:
mov ax, array[di]
and ax, 00ffh ; the value of negative numbers is in the low byte
mov bx, 00ffh ; we set the max. value a neg. number can have to bx
sub bx, ax ; Substract the neg. max. value from the current number and
; store to bx.
add bx, 1 ; The math. formula to find the absolute value of a negative
; number is: FFh - (low byte hex number) + 1 =
; = (absolute value of negative number).
; Or: 256 - (low byte hex) = (absolute value of negative number)
; But because we can't represent 256 as a hex number, we first
; perform the substraction with 255, and at the end we add 1,
; that is, equivalent to 256 as (255+1).
jmp l_display
display_array endp
HEX2ASCII PROC NEAR
; Converts a word variable to ASCII
; Writes the results in the BUFFER (4 bytes) variable
; PARAMETERS
; gets a word variable from the stack
push bp
mov bp, sp
mov ax, [bp+4] ; take input variable
; it takes the ax value that was pushed
; last before this subroutine was called
push cx
mov cx, 4
mov bp, cx
H2A1:
push ax
and al, 0Fh
add al, 30h
cmp al, '9'
jbe H2A2
add al, 7h
H2A2:
dec cx
mov bp, cx
mov buffer[bp], al
pop ax
ror ax, 4
jnz H2A1
pop cx
pop bp
ret 2 ; ... may optionally specify an immediate operand,
; by adding this constant to the stack pointer,
; they effectively remove any arguments that the
; calling program pushed on the stack before
; the execution of the call instruction.
; Effectively, in this case, it removes the data
; that was pushed onto the stack (i.e. 'push ax'),
; before this subroutine was called.
HEX2ASCII ENDP
code ends
end start