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)
16 mov bx, ax ; Move location of string to BX
21 cmp byte [bx], 0 ; Zero (end of string) yet?
23 inc bx ; If not, keep adding
29 mov word [.tmp_counter
], cx ; Store count before restoring other registers
32 mov ax, [.tmp_counter
] ; Put count back into AX before returning
39 ; ------------------------------------------------------------------
40 ; os_string_reverse -- Reverse the characters in a string
41 ; IN: SI = string location
46 cmp byte [si], 0 ; Don't attempt to reverse empty string
54 dec di ; DI now points to last char in string
57 mov byte al, [si] ; Swap bytes
63 inc si ; Move towards string centre
66 cmp di, si ; Both reached the centre?
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:
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)
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:
137 ; ------------------------------------------------------------------
138 ; os_string_uppercase -- Convert zero-terminated string to upper case
139 ; IN/OUT: AX = string location
144 mov si, ax ; Use SI to access string
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?
155 sub byte [si], 20h ; If so, convert input char to upper case
169 ; ------------------------------------------------------------------
170 ; os_string_lowercase -- Convert zero-terminated string to lower case
171 ; IN/OUT: AX = string location
176 mov si, ax ; Use SI to access string
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?
187 add byte [si], 20h ; If so, convert input char to lower case
201 ; ------------------------------------------------------------------
202 ; os_string_copy -- Copy one string into another
203 ; IN/OUT: SI = source, DI = destination (programmer ensure sufficient room)
209 mov al, [si] ; Transfer contents (at least one byte terminator)
213 cmp byte al, 0 ; If source string is empty, quit out
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
236 ; ------------------------------------------------------------------
237 ; os_string_join -- Join two strings into a third string
238 ; IN/OUT: AX = string one, BX = string two, CX = destination string
243 mov si, ax ; Put first string into CX
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
259 ; ------------------------------------------------------------------
260 ; os_string_chomp -- Strip leading and trailing spaces from a string
261 ; IN: AX = string location
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
279 cmp cx, 0 ; No leading spaces?
282 mov si, di ; Address of first non-space character
283 mov di, dx ; DI = original string start
286 mov al, [si] ; Copy SI into DI
287 mov [di], al ; Including terminator
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'
302 add si, ax ; Move to end of string
308 mov byte [si], 0 ; Fill end spaces with 0s
309 jmp .more
; (First 0 will be the string terminator)
316 ; ------------------------------------------------------------------
317 ; os_string_strip -- Removes specified character from a string (max 255 chars)
318 ; IN: SI = string location, AL = character to remove
325 mov bl, al ; Copy the char into BL since LODSB and STOSB use AL
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
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
351 mov al, [si] ; Retrieve string contents
354 cmp al, bl ; Compare characters at current location
357 cmp al, 0 ; End of first string? Must also be end of second
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
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
386 mov al, [si] ; Retrieve string contents
389 cmp al, bl ; Compare characters at current location
392 cmp al, 0 ; End of first string? Must also be end of second
398 dec cl ; If we've lasted through our char count
399 cmp cl, 0 ; Then the bits of the string match!
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
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
425 mov ax, si ; AX = start of first string
427 mov bx, 0 ; By default, other strings start empty
431 push ax ; Save to retrieve at end
435 cmp al, 0 ; End of string?
437 cmp al, ' ' ; A space?
440 mov byte [si], 0 ; If so, zero-terminate this bit of the string
442 inc si ; Store start of next string in BX
445 .
loop2: ; Repeat the above for CX and DX...
476 ; ------------------------------------------------------------------
477 ; os_string_to_int -- Convert decimal string to integer value
478 ; IN: SI = string location (max 5 chars, up to '65536')
484 mov ax, si ; First, get length of string
485 call os_string_length
487 add si, ax ; Work from rightmost char in string
490 mov cx, ax ; Use string length as counter
492 mov bx, 0 ; BX will be the final number
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
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
]
516 mov word [.multiplier
], ax
519 dec cx ; Any more chars?
522 dec si ; Move back a char in the string
537 ; ------------------------------------------------------------------
538 ; os_int_to_string -- Convert unsigned integer to string
539 ; IN: AX = signed int
540 ; OUT: AX = string location
546 mov bx, 10 ; Set BX 10, for division and mod
547 mov di, .t
; Get our pointer ready
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
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
564 mov byte [di], 0 ; Zero-terminate string
567 mov ax, .t
; Return location of string
574 ; ------------------------------------------------------------------
575 ; os_sint_to_string -- Convert signed integer to string
576 ; IN: AX = signed int
577 ; OUT: AX = string location
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
590 neg ax ; Make AX positive
591 mov byte [.t
], '-' ; Add a minus sign to our string
592 inc di ; Update the index
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
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
608 mov byte [di], 0 ; Zero-terminate string
611 mov ax, .t
; Return location of string
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:
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
633 cmp bx, 0 ; Base = 0 produces overflow, return null
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
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
649 cmp cx, 9 ; Eliminate punctuation between '9' and 'A'
655 add cx, '0' ; Convert to ASCII
657 push ax ; Load this ASCII digit into the beginning of the string
660 call os_string_length
; AX = length of string, less terminator
662 add di, ax ; DI = end of string
663 inc ax ; AX = nunber of characters to move, including terminator
666 mov bl, [di] ; Put digits in correct order
674 mov [si], cl ; Last digit (LSD) will print first (on left)
677 mov cx, dx ; DX = high word, again
678 or cx, ax ; Nothing left?
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
701 ; ------------------------------------------------------------------
702 ; os_get_time_string -- Get current time in a string (eg '10:25')
703 ; IN/OUT: BX = string location
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
716 mov ah, 2 ; BIOS was updating (~1 in 500 chance), so try again
720 mov al, ch ; Convert hours to integer for AM/PM test
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
730 call .add_digit
; BCD already in 24-hour format
736 cmp dx, 0 ; If 00mm, make 12 AM
739 cmp dx, 10 ; Before 1000, OK to store 1 digit
742 cmp dx, 12 ; Between 1000 and 1300, OK to store 2 digits
745 mov ax, dx ; Change from 24 to 12-hour format
754 jmp short .twelve_st2
; 10-11 PM
761 call .add_digit
; Modified BCD, 2-digit hour
766 mov al, ':' ; Time separator (12-hr format)
771 shr al, 4 ; Tens digit - move higher BCD number into lower bits
772 and cl, 0Fh
; Ones digit
777 mov al, ' ' ; Separate time designation
780 mov si, .hours_string
; Assume 24-hr format
781 test byte [fmt_12_24
], 0FFh
784 mov si, .pm_string
; Assume PM
785 cmp dx, 12 ; Test for AM/PM
788 mov si, .am_string
; Was actually AM
791 lodsb ; Copy designation, including terminator
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
819 test al, 80h ; ASCII months (bit 7)?
822 and ax, 7F03h
; 7-bit ASCII separator and format number
826 and ax, 0003 ; Ensure separator is clear
829 cmp al, 3 ; Only allow 0, 1 and 2
838 ; ------------------------------------------------------------------
839 ; os_get_date_string -- Get current date in a string (eg '12/31/2007')
840 ; IN/OUT: BX = string location
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
855 mov ah, 4 ; BIOS was updating (~1 in 500 chance), so try again
859 cmp bl, 2 ; YYYY/MM/DD format, suitable for sorting
862 mov ah, ch ; Always provide 4-digit year
865 call .add_2digits
; And '/' as separator
869 mov ah, dh ; Always 2-digit month
871 mov al, '/' ; And '/' as separator
874 mov ah, dl ; Always 2-digit day
879 cmp bl, 1 ; D/M/Y format (military and Europe)
889 mov al, ' ' ; If ASCII months, use space as separator
892 stosb ; Day-month separator
898 call .add_month
; Yes, add to string
901 jmp short .fmt1_century
904 call .add_1or2digits
; No, use digits and separator
909 mov ah, ch ; Century present?
913 call .add_1or2digits
; Yes, add it to string (most likely 2 digits)
917 call .add_2digits
; At least 2 digits for year, always
921 .
do_fmt0: ; Default format, M/D/Y (US and others)
926 call .add_month
; Yes, add to string and space
932 call .add_1or2digits
; No, use digits and separator
944 mov al, ',' ; Yes, separator = comma space
952 mov ah, ch ; Century present?
956 call .add_1or2digits
; Yes, add it to string (most likely 2 digits)
960 call .add_2digits
; At least 2 digits for year, always
964 mov ax, 0 ; Terminate date string
984 mov al, ah ; Convert AH to 2 ASCII digits
993 add al, '0' ; Convert AL to ASCII
994 stosb ; Put into string buffer
1000 mov al, ah ; Convert month to integer to index print table
1002 dec al ; January = 0
1003 mov bl, 4 ; Multiply month by 4 characters/month
1009 cmp byte [di-1], ' ' ; May?
1010 jne .done_month
; Yes, eliminate extra space
1018 .months
db 'Jan.Feb.Mar.Apr.May JuneJulyAug.SeptOct.Nov.Dec.'
1021 ; ==================================================================