* mikeOS 16 bit and amd64 baremetal
[mascara-docs.git] / i86 / mikeos-4.1.2 / source / features / string.asm
blob58b7887301b4633bbeb3a1989e84b632d9f1b047
1 ; ==================================================================
2 ; MikeOS -- The Mike Operating System kernel
3 ; Copyright (C) 2006 - 2011 MikeOS Developers -- see doc/LICENSE.TXT
5 ; STRING MANIPULATION ROUTINES
6 ; ==================================================================
8 ; ------------------------------------------------------------------
9 ; os_string_length -- Return length of a string
10 ; IN: AX = string location
11 ; OUT AX = length (other regs preserved)
13 os_string_length:
14 pusha
16 mov bx, ax ; Move location of string to BX
18 mov cx, 0 ; Counter
20 .more:
21 cmp byte [bx], 0 ; Zero (end of string) yet?
22 je .done
23 inc bx ; If not, keep adding
24 inc cx
25 jmp .more
28 .done:
29 mov word [.tmp_counter], cx ; Store count before restoring other registers
30 popa
32 mov ax, [.tmp_counter] ; Put count back into AX before returning
33 ret
36 .tmp_counter dw 0
39 ; ------------------------------------------------------------------
40 ; os_string_reverse -- Reverse the characters in a string
41 ; IN: SI = string location
43 os_string_reverse:
44 pusha
46 cmp byte [si], 0 ; Don't attempt to reverse empty string
47 je .end
49 mov ax, si
50 call os_string_length
52 mov di, si
53 add di, ax
54 dec di ; DI now points to last char in string
56 .loop:
57 mov byte al, [si] ; Swap bytes
58 mov byte bl, [di]
60 mov byte [si], bl
61 mov byte [di], al
63 inc si ; Move towards string centre
64 dec di
66 cmp di, si ; Both reached the centre?
67 ja .loop
69 .end:
70 popa
71 ret
74 ; ------------------------------------------------------------------
75 ; os_find_char_in_string -- Find location of character in a string
76 ; IN: SI = string location, AL = character to find
77 ; OUT: AX = location in string, or 0 if char not present
79 os_find_char_in_string:
80 pusha
82 mov cx, 1 ; Counter -- start at first char (we count
83 ; from 1 in chars here, so that we can
84 ; return 0 if the source char isn't found)
86 .more:
87 cmp byte [si], al
88 je .done
89 cmp byte [si], 0
90 je .notfound
91 inc si
92 inc cx
93 jmp .more
95 .done:
96 mov [.tmp], cx
97 popa
98 mov ax, [.tmp]
99 ret
101 .notfound:
102 popa
103 mov ax, 0
107 .tmp dw 0
110 ; ------------------------------------------------------------------
111 ; os_string_charchange -- Change instances of character in a string
112 ; IN: SI = string, AL = char to find, BL = char to replace with
114 os_string_charchange:
115 pusha
117 mov cl, al
119 .loop:
120 mov byte al, [si]
121 cmp al, 0
122 je .finish
123 cmp al, cl
124 jne .nochange
126 mov byte [si], bl
128 .nochange:
129 inc si
130 jmp .loop
132 .finish:
133 popa
137 ; ------------------------------------------------------------------
138 ; os_string_uppercase -- Convert zero-terminated string to upper case
139 ; IN/OUT: AX = string location
141 os_string_uppercase:
142 pusha
144 mov si, ax ; Use SI to access string
146 .more:
147 cmp byte [si], 0 ; Zero-termination of string?
148 je .done ; If so, quit
150 cmp byte [si], 'a' ; In the lower case A to Z range?
151 jb .noatoz
152 cmp byte [si], 'z'
153 ja .noatoz
155 sub byte [si], 20h ; If so, convert input char to upper case
157 inc si
158 jmp .more
160 .noatoz:
161 inc si
162 jmp .more
164 .done:
165 popa
169 ; ------------------------------------------------------------------
170 ; os_string_lowercase -- Convert zero-terminated string to lower case
171 ; IN/OUT: AX = string location
173 os_string_lowercase:
174 pusha
176 mov si, ax ; Use SI to access string
178 .more:
179 cmp byte [si], 0 ; Zero-termination of string?
180 je .done ; If so, quit
182 cmp byte [si], 'A' ; In the upper case A to Z range?
183 jb .noatoz
184 cmp byte [si], 'Z'
185 ja .noatoz
187 add byte [si], 20h ; If so, convert input char to lower case
189 inc si
190 jmp .more
192 .noatoz:
193 inc si
194 jmp .more
196 .done:
197 popa
201 ; ------------------------------------------------------------------
202 ; os_string_copy -- Copy one string into another
203 ; IN/OUT: SI = source, DI = destination (programmer ensure sufficient room)
205 os_string_copy:
206 pusha
208 .more:
209 mov al, [si] ; Transfer contents (at least one byte terminator)
210 mov [di], al
211 inc si
212 inc di
213 cmp byte al, 0 ; If source string is empty, quit out
214 jne .more
216 .done:
217 popa
221 ; ------------------------------------------------------------------
222 ; os_string_truncate -- Chop string down to specified number of characters
223 ; IN: SI = string location, AX = number of characters
224 ; OUT: String modified, registers preserved
226 os_string_truncate:
227 pusha
229 add si, ax
230 mov byte [si], 0
232 popa
236 ; ------------------------------------------------------------------
237 ; os_string_join -- Join two strings into a third string
238 ; IN/OUT: AX = string one, BX = string two, CX = destination string
240 os_string_join:
241 pusha
243 mov si, ax ; Put first string into CX
244 mov di, cx
245 call os_string_copy
247 call os_string_length ; Get length of first string
249 add cx, ax ; Position at end of first string
251 mov si, bx ; Add second string onto it
252 mov di, cx
253 call os_string_copy
255 popa
259 ; ------------------------------------------------------------------
260 ; os_string_chomp -- Strip leading and trailing spaces from a string
261 ; IN: AX = string location
263 os_string_chomp:
264 pusha
266 mov dx, ax ; Save string location
268 mov di, ax ; Put location into DI
269 mov cx, 0 ; Space counter
271 .keepcounting: ; Get number of leading spaces into BX
272 cmp byte [di], ' '
273 jne .counted
274 inc cx
275 inc di
276 jmp .keepcounting
278 .counted:
279 cmp cx, 0 ; No leading spaces?
280 je .finished_copy
282 mov si, di ; Address of first non-space character
283 mov di, dx ; DI = original string start
285 .keep_copying:
286 mov al, [si] ; Copy SI into DI
287 mov [di], al ; Including terminator
288 cmp al, 0
289 je .finished_copy
290 inc si
291 inc di
292 jmp .keep_copying
294 .finished_copy:
295 mov ax, dx ; AX = original string start
297 call os_string_length
298 cmp ax, 0 ; If empty or all blank, done, return 'null'
299 je .done
301 mov si, dx
302 add si, ax ; Move to end of string
304 .more:
305 dec si
306 cmp byte [si], ' '
307 jne .done
308 mov byte [si], 0 ; Fill end spaces with 0s
309 jmp .more ; (First 0 will be the string terminator)
311 .done:
312 popa
316 ; ------------------------------------------------------------------
317 ; os_string_strip -- Removes specified character from a string (max 255 chars)
318 ; IN: SI = string location, AL = character to remove
320 os_string_strip:
321 pusha
323 mov di, si
325 mov bl, al ; Copy the char into BL since LODSB and STOSB use AL
326 .nextchar:
327 lodsb
328 stosb
329 cmp al, 0 ; Check if we reached the end of the string
330 je .finish ; If so, bail out
331 cmp al, bl ; Check to see if the character we read is the interesting char
332 jne .nextchar ; If not, skip to the next character
334 .skip: ; If so, the fall through to here
335 dec di ; Decrement DI so we overwrite on the next pass
336 jmp .nextchar
338 .finish:
339 popa
342 ; ------------------------------------------------------------------
343 ; os_string_compare -- See if two strings match
344 ; IN: SI = string one, DI = string two
345 ; OUT: carry set if same, clear if different
347 os_string_compare:
348 pusha
350 .more:
351 mov al, [si] ; Retrieve string contents
352 mov bl, [di]
354 cmp al, bl ; Compare characters at current location
355 jne .not_same
357 cmp al, 0 ; End of first string? Must also be end of second
358 je .terminated
360 inc si
361 inc di
362 jmp .more
365 .not_same: ; If unequal lengths with same beginning, the byte
366 popa ; comparison fails at shortest string terminator
367 clc ; Clear carry flag
371 .terminated: ; Both strings terminated at the same position
372 popa
373 stc ; Set carry flag
377 ; ------------------------------------------------------------------
378 ; os_string_strincmp -- See if two strings match up to set number of chars
379 ; IN: SI = string one, DI = string two, CL = chars to check
380 ; OUT: carry set if same, clear if different
382 os_string_strincmp:
383 pusha
385 .more:
386 mov al, [si] ; Retrieve string contents
387 mov bl, [di]
389 cmp al, bl ; Compare characters at current location
390 jne .not_same
392 cmp al, 0 ; End of first string? Must also be end of second
393 je .terminated
395 inc si
396 inc di
398 dec cl ; If we've lasted through our char count
399 cmp cl, 0 ; Then the bits of the string match!
400 je .terminated
402 jmp .more
405 .not_same: ; If unequal lengths with same beginning, the byte
406 popa ; comparison fails at shortest string terminator
407 clc ; Clear carry flag
411 .terminated: ; Both strings terminated at the same position
412 popa
413 stc ; Set carry flag
417 ; ------------------------------------------------------------------
418 ; os_string_parse -- Take string (eg "run foo bar baz") and return
419 ; pointers to zero-terminated strings (eg AX = "run", BX = "foo" etc.)
420 ; IN: SI = string; OUT: AX, BX, CX, DX = individual strings
422 os_string_parse:
423 push si
425 mov ax, si ; AX = start of first string
427 mov bx, 0 ; By default, other strings start empty
428 mov cx, 0
429 mov dx, 0
431 push ax ; Save to retrieve at end
433 .loop1:
434 lodsb ; Get a byte
435 cmp al, 0 ; End of string?
436 je .finish
437 cmp al, ' ' ; A space?
438 jne .loop1
439 dec si
440 mov byte [si], 0 ; If so, zero-terminate this bit of the string
442 inc si ; Store start of next string in BX
443 mov bx, si
445 .loop2: ; Repeat the above for CX and DX...
446 lodsb
447 cmp al, 0
448 je .finish
449 cmp al, ' '
450 jne .loop2
451 dec si
452 mov byte [si], 0
454 inc si
455 mov cx, si
457 .loop3:
458 lodsb
459 cmp al, 0
460 je .finish
461 cmp al, ' '
462 jne .loop3
463 dec si
464 mov byte [si], 0
466 inc si
467 mov dx, si
469 .finish:
470 pop ax
472 pop si
476 ; ------------------------------------------------------------------
477 ; os_string_to_int -- Convert decimal string to integer value
478 ; IN: SI = string location (max 5 chars, up to '65536')
479 ; OUT: AX = number
481 os_string_to_int:
482 pusha
484 mov ax, si ; First, get length of string
485 call os_string_length
487 add si, ax ; Work from rightmost char in string
488 dec si
490 mov cx, ax ; Use string length as counter
492 mov bx, 0 ; BX will be the final number
493 mov ax, 0
496 ; As we move left in the string, each char is a bigger multiple. The
497 ; right-most character is a multiple of 1, then next (a char to the
498 ; left) a multiple of 10, then 100, then 1,000, and the final (and
499 ; leftmost char) in a five-char number would be a multiple of 10,000
501 mov word [.multiplier], 1 ; Start with multiples of 1
503 .loop:
504 mov ax, 0
505 mov byte al, [si] ; Get character
506 sub al, 48 ; Convert from ASCII to real number
508 mul word [.multiplier] ; Multiply by our multiplier
510 add bx, ax ; Add it to BX
512 push ax ; Multiply our multiplier by 10 for next char
513 mov word ax, [.multiplier]
514 mov dx, 10
515 mul dx
516 mov word [.multiplier], ax
517 pop ax
519 dec cx ; Any more chars?
520 cmp cx, 0
521 je .finish
522 dec si ; Move back a char in the string
523 jmp .loop
525 .finish:
526 mov word [.tmp], bx
527 popa
528 mov word ax, [.tmp]
533 .multiplier dw 0
534 .tmp dw 0
537 ; ------------------------------------------------------------------
538 ; os_int_to_string -- Convert unsigned integer to string
539 ; IN: AX = signed int
540 ; OUT: AX = string location
542 os_int_to_string:
543 pusha
545 mov cx, 0
546 mov bx, 10 ; Set BX 10, for division and mod
547 mov di, .t ; Get our pointer ready
549 .push:
550 mov dx, 0
551 div bx ; Remainder in DX, quotient in AX
552 inc cx ; Increase pop loop counter
553 push dx ; Push remainder, so as to reverse order when popping
554 test ax, ax ; Is quotient zero?
555 jnz .push ; If not, loop again
556 .pop:
557 pop dx ; Pop off values in reverse order, and add 48 to make them digits
558 add dl, '0' ; And save them in the string, increasing the pointer each time
559 mov [di], dl
560 inc di
561 dec cx
562 jnz .pop
564 mov byte [di], 0 ; Zero-terminate string
566 popa
567 mov ax, .t ; Return location of string
571 .t times 7 db 0
574 ; ------------------------------------------------------------------
575 ; os_sint_to_string -- Convert signed integer to string
576 ; IN: AX = signed int
577 ; OUT: AX = string location
579 os_sint_to_string:
580 pusha
582 mov cx, 0
583 mov bx, 10 ; Set BX 10, for division and mod
584 mov di, .t ; Get our pointer ready
586 test ax, ax ; Find out if X > 0 or not, force a sign
587 js .neg ; If negative...
588 jmp .push ; ...or if positive
589 .neg:
590 neg ax ; Make AX positive
591 mov byte [.t], '-' ; Add a minus sign to our string
592 inc di ; Update the index
593 .push:
594 mov dx, 0
595 div bx ; Remainder in DX, quotient in AX
596 inc cx ; Increase pop loop counter
597 push dx ; Push remainder, so as to reverse order when popping
598 test ax, ax ; Is quotient zero?
599 jnz .push ; If not, loop again
600 .pop:
601 pop dx ; Pop off values in reverse order, and add 48 to make them digits
602 add dl, '0' ; And save them in the string, increasing the pointer each time
603 mov [di], dl
604 inc di
605 dec cx
606 jnz .pop
608 mov byte [di], 0 ; Zero-terminate string
610 popa
611 mov ax, .t ; Return location of string
615 .t times 7 db 0
618 ; ------------------------------------------------------------------
619 ; os_long_int_to_string -- Convert value in DX:AX to string
620 ; IN: DX:AX = long unsigned integer, BX = number base, DI = string location
621 ; OUT: DI = location of converted string
623 os_long_int_to_string:
624 pusha
626 mov si, di ; Prepare for later data movement
628 mov word [di], 0 ; Terminate string, creates 'null'
630 cmp bx, 37 ; Base > 37 or < 0 not supported, return null
631 ja .done
633 cmp bx, 0 ; Base = 0 produces overflow, return null
634 je .done
636 .conversion_loop:
637 mov cx, 0 ; Zero extend unsigned integer, number = CX:DX:AX
638 ; If number = 0, goes through loop once and stores '0'
640 xchg ax, cx ; Number order DX:AX:CX for high order division
641 xchg ax, dx
642 div bx ; AX = high quotient, DX = high remainder
644 xchg ax, cx ; Number order for low order division
645 div bx ; CX = high quotient, AX = low quotient, DX = remainder
646 xchg cx, dx ; CX = digit to send
648 .save_digit:
649 cmp cx, 9 ; Eliminate punctuation between '9' and 'A'
650 jle .convert_digit
652 add cx, 'A'-'9'-1
654 .convert_digit:
655 add cx, '0' ; Convert to ASCII
657 push ax ; Load this ASCII digit into the beginning of the string
658 push bx
659 mov ax, si
660 call os_string_length ; AX = length of string, less terminator
661 mov di, si
662 add di, ax ; DI = end of string
663 inc ax ; AX = nunber of characters to move, including terminator
665 .move_string_up:
666 mov bl, [di] ; Put digits in correct order
667 mov [di+1], bl
668 dec di
669 dec ax
670 jnz .move_string_up
672 pop bx
673 pop ax
674 mov [si], cl ; Last digit (LSD) will print first (on left)
676 .test_end:
677 mov cx, dx ; DX = high word, again
678 or cx, ax ; Nothing left?
679 jnz .conversion_loop
681 .done:
682 popa
686 ; ------------------------------------------------------------------
687 ; os_set_time_fmt -- Set time reporting format (eg '10:25 AM' or '2300 hours')
688 ; IN: AL = format flag, 0 = 12-hr format
690 os_set_time_fmt:
691 pusha
692 cmp al, 0
693 je .store
694 mov al, 0FFh
695 .store:
696 mov [fmt_12_24], al
697 popa
701 ; ------------------------------------------------------------------
702 ; os_get_time_string -- Get current time in a string (eg '10:25')
703 ; IN/OUT: BX = string location
705 os_get_time_string:
706 pusha
708 mov di, bx ; Location to place time string
710 clc ; For buggy BIOSes
711 mov ah, 2 ; Get time data from BIOS in BCD format
712 int 1Ah
713 jnc .read
716 mov ah, 2 ; BIOS was updating (~1 in 500 chance), so try again
717 int 1Ah
719 .read:
720 mov al, ch ; Convert hours to integer for AM/PM test
721 call os_bcd_to_int
722 mov dx, ax ; Save
724 mov al, ch ; Hour
725 shr al, 4 ; Tens digit - move higher BCD number into lower bits
726 and ch, 0Fh ; Ones digit
727 test byte [fmt_12_24], 0FFh
728 jz .twelve_hr
730 call .add_digit ; BCD already in 24-hour format
731 mov al, ch
732 call .add_digit
733 jmp short .minutes
735 .twelve_hr:
736 cmp dx, 0 ; If 00mm, make 12 AM
737 je .midnight
739 cmp dx, 10 ; Before 1000, OK to store 1 digit
740 jl .twelve_st1
742 cmp dx, 12 ; Between 1000 and 1300, OK to store 2 digits
743 jle .twelve_st2
745 mov ax, dx ; Change from 24 to 12-hour format
746 sub ax, 12
747 mov bl, 10
748 div bl
749 mov ch, ah
751 cmp al, 0 ; 1-9 PM
752 je .twelve_st1
754 jmp short .twelve_st2 ; 10-11 PM
756 .midnight:
757 mov al, 1
758 mov ch, 2
760 .twelve_st2:
761 call .add_digit ; Modified BCD, 2-digit hour
762 .twelve_st1:
763 mov al, ch
764 call .add_digit
766 mov al, ':' ; Time separator (12-hr format)
767 stosb
769 .minutes:
770 mov al, cl ; Minute
771 shr al, 4 ; Tens digit - move higher BCD number into lower bits
772 and cl, 0Fh ; Ones digit
773 call .add_digit
774 mov al, cl
775 call .add_digit
777 mov al, ' ' ; Separate time designation
778 stosb
780 mov si, .hours_string ; Assume 24-hr format
781 test byte [fmt_12_24], 0FFh
782 jnz .copy
784 mov si, .pm_string ; Assume PM
785 cmp dx, 12 ; Test for AM/PM
786 jg .copy
788 mov si, .am_string ; Was actually AM
790 .copy:
791 lodsb ; Copy designation, including terminator
792 stosb
793 cmp al, 0
794 jne .copy
796 popa
800 .add_digit:
801 add al, '0' ; Convert to ASCII
802 stosb ; Put into string buffer
806 .hours_string db 'hours', 0
807 .am_string db 'AM', 0
808 .pm_string db 'PM', 0
811 ; ------------------------------------------------------------------
812 ; os_set_date_fmt -- Set date reporting format (M/D/Y, D/M/Y or Y/M/D - 0, 1, 2)
813 ; IN: AX = format flag, 0-2
814 ; If AX bit 7 = 1 = use name for months
815 ; If AX bit 7 = 0, high byte = separator character
817 os_set_date_fmt:
818 pusha
819 test al, 80h ; ASCII months (bit 7)?
820 jnz .fmt_clear
822 and ax, 7F03h ; 7-bit ASCII separator and format number
823 jmp short .fmt_test
825 .fmt_clear:
826 and ax, 0003 ; Ensure separator is clear
828 .fmt_test:
829 cmp al, 3 ; Only allow 0, 1 and 2
830 jae .leave
831 mov [fmt_date], ax
833 .leave:
834 popa
838 ; ------------------------------------------------------------------
839 ; os_get_date_string -- Get current date in a string (eg '12/31/2007')
840 ; IN/OUT: BX = string location
842 os_get_date_string:
843 pusha
845 mov di, bx ; Store string location for now
846 mov bx, [fmt_date] ; BL = format code
847 and bx, 7F03h ; BH = separator, 0 = use month names
849 clc ; For buggy BIOSes
850 mov ah, 4 ; Get date data from BIOS in BCD format
851 int 1Ah
852 jnc .read
855 mov ah, 4 ; BIOS was updating (~1 in 500 chance), so try again
856 int 1Ah
858 .read:
859 cmp bl, 2 ; YYYY/MM/DD format, suitable for sorting
860 jne .try_fmt1
862 mov ah, ch ; Always provide 4-digit year
863 call .add_2digits
864 mov ah, cl
865 call .add_2digits ; And '/' as separator
866 mov al, '/'
867 stosb
869 mov ah, dh ; Always 2-digit month
870 call .add_2digits
871 mov al, '/' ; And '/' as separator
872 stosb
874 mov ah, dl ; Always 2-digit day
875 call .add_2digits
876 jmp short .done
878 .try_fmt1:
879 cmp bl, 1 ; D/M/Y format (military and Europe)
880 jne .do_fmt0
882 mov ah, dl ; Day
883 call .add_1or2digits
885 mov al, bh
886 cmp bh, 0
887 jne .fmt1_day
889 mov al, ' ' ; If ASCII months, use space as separator
891 .fmt1_day:
892 stosb ; Day-month separator
894 mov ah, dh ; Month
895 cmp bh, 0 ; ASCII?
896 jne .fmt1_month
898 call .add_month ; Yes, add to string
899 mov ax, ', '
900 stosw
901 jmp short .fmt1_century
903 .fmt1_month:
904 call .add_1or2digits ; No, use digits and separator
905 mov al, bh
906 stosb
908 .fmt1_century:
909 mov ah, ch ; Century present?
910 cmp ah, 0
911 je .fmt1_year
913 call .add_1or2digits ; Yes, add it to string (most likely 2 digits)
915 .fmt1_year:
916 mov ah, cl ; Year
917 call .add_2digits ; At least 2 digits for year, always
919 jmp short .done
921 .do_fmt0: ; Default format, M/D/Y (US and others)
922 mov ah, dh ; Month
923 cmp bh, 0 ; ASCII?
924 jne .fmt0_month
926 call .add_month ; Yes, add to string and space
927 mov al, ' '
928 stosb
929 jmp short .fmt0_day
931 .fmt0_month:
932 call .add_1or2digits ; No, use digits and separator
933 mov al, bh
934 stosb
936 .fmt0_day:
937 mov ah, dl ; Day
938 call .add_1or2digits
940 mov al, bh
941 cmp bh, 0 ; ASCII?
942 jne .fmt0_day2
944 mov al, ',' ; Yes, separator = comma space
945 stosb
946 mov al, ' '
948 .fmt0_day2:
949 stosb
951 .fmt0_century:
952 mov ah, ch ; Century present?
953 cmp ah, 0
954 je .fmt0_year
956 call .add_1or2digits ; Yes, add it to string (most likely 2 digits)
958 .fmt0_year:
959 mov ah, cl ; Year
960 call .add_2digits ; At least 2 digits for year, always
963 .done:
964 mov ax, 0 ; Terminate date string
965 stosw
967 popa
971 .add_1or2digits:
972 test ah, 0F0h
973 jz .only_one
974 call .add_2digits
975 jmp short .two_done
976 .only_one:
977 mov al, ah
978 and al, 0Fh
979 call .add_digit
980 .two_done:
983 .add_2digits:
984 mov al, ah ; Convert AH to 2 ASCII digits
985 shr al, 4
986 call .add_digit
987 mov al, ah
988 and al, 0Fh
989 call .add_digit
992 .add_digit:
993 add al, '0' ; Convert AL to ASCII
994 stosb ; Put into string buffer
997 .add_month:
998 push bx
999 push cx
1000 mov al, ah ; Convert month to integer to index print table
1001 call os_bcd_to_int
1002 dec al ; January = 0
1003 mov bl, 4 ; Multiply month by 4 characters/month
1004 mul bl
1005 mov si, .months
1006 add si, ax
1007 mov cx, 4
1008 rep movsb
1009 cmp byte [di-1], ' ' ; May?
1010 jne .done_month ; Yes, eliminate extra space
1011 dec di
1012 .done_month:
1013 pop cx
1014 pop bx
1018 .months db 'Jan.Feb.Mar.Apr.May JuneJulyAug.SeptOct.Nov.Dec.'
1021 ; ==================================================================