;-------------------------------------------------------------------------------------------------------- ;- ;- Name: RFS_Utilities.asm ;- Created: September 2019 ;- Author(s): Philip Smart ;- Description: Sharp MZ series tzfs (tranZPUter Filing System). ;- This assembly language program is a branch from the original RFS written for the ;- MZ80A_RFS upgrade board. It is adapted to work within the similar yet different ;- environment of the tranZPUter SW which has a large RAM capacity (512K) and an ;- I/O processor in the K64F/ZPU. ;- ;- Credits: ;- Copyright: (c) 2018-2023 Philip Smart ;- ;- History: May 2020 - Branch taken from RFS v2.0 and adapted for the tranZPUter SW. ;- Feb 2023 - TZFS now running on FusionX. Small changes to ensure compatibility. ;- ;-------------------------------------------------------------------------------------------------------- ;- This source file is free software: you can redistribute it and-or modify ;- it under the terms of the GNU General Public License as published ;- by the Free Software Foundation, either version 3 of the License, or ;- (at your option) any later version. ;- ;- This source file is distributed in the hope that it will be useful, ;- but WITHOUT ANY WARRANTY; without even the implied warranty of ;- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;- GNU General Public License for more details. ;- ;- You should have received a copy of the GNU General Public License ;- along with this program. If not, see . ;-------------------------------------------------------------------------------------------------------- ; Comparing Strings ; IN HL Address of string1. ; DE Address of string2. ; BC Max bytes to compare, 0x00 or 0x0d will early terminate. ; OUT zero Set if string1 = string2, reset if string1 != string2. ; carry Set if string1 > string2, reset if string1 <= string2. CMPSTRING: IF USE_CMPSTRING = 1 PUSH HL PUSH DE CMPSTR1: LD A, (DE) ; Compare bytes. CP 000h ; Check for end of string. JR Z, CMPSTR3 CP 00Dh JR Z, CMPSTR3 CPI ; Compare bytes. JR NZ, CMPSTR2 ; If (HL) != (DE), abort. INC DE ; Update pointer. JP PE, CMPSTR1 ; Next byte if BC not zero. CMPSTR2: DEC HL CP (HL) ; Compare again to affect carry. CMPSTR4: POP DE POP HL RET CMPSTR3: LD A, (HL) CP 000h ; Check for end of string. JR Z, CMPSTR4 CP 00Dh JR Z, CMPSTR4 SCF ; String 1 greater than string 2 JR CMPSTR4 ENDIF ; IN HL Address of source string, length-prefixed. ; DE Address of destination string, length-prefixed. ; B Start index. 1 = first character. ; C Length of substring to return. ; ; OUT carry Set if an error condition happened: ; If B is zero, then uses index of 1. ; If index > source length, an empty string is returned. ; If index + return length > source length, returns all ; characters from index to end-of-string. SUBSTRING: IF USE_SUBSTRING = 1 PUSH DE ; It would be convenient to keep DE pointing to ; the start of the destination string OR A ; Boolean OR resets carry PUSH AF ; Save carry LD A, B ; Is index beyond source length? CP (HL) DEC A ; Decrement A so NC can be used JR NC,SUBST3 ADD A, C ; If index+len is > 255, error JR C, SUBST1 INC A ; Increment A so C can be used CP (HL) ; If index+len is beyond source length, then error JR C, SUBST2 SUBST1: POP AF ; Set carry flag SCF PUSH AF LD A, (HL) ; Get source length SUB B ; Subtract start index INC A ; Compensate LD C, A ; New size of string SUBST2: LD A, C ; Size of sting to get LD (DE), A ; Save length index INC DE ; To body of string LD A, B ; Get index LD B, 0 ; Zero-extend BC for LDIR ADD A, L ; This is a sneaky way to add A to HL LD L, A ; without using up another 16-bit register ADC A, H ; SUB L ; LD H, A ; LDIR ; Copy substring over POP AF ; Restore flags POP DE ; Restore destination RET SUBST3: XOR A ; Set a length index of zero LD (DE), A POP AF ; Clean off stack and set carry POP DE SCF RET ENDIF ; IN HL Address of string to look in, length prefixed. ; DE Address of string to find, length prefixed. ; ; OUT ; If found: ; A Offset into look-up string where the target string was found. ; The first byte (ignoring length prefix) is offset 1. ; carry Reset. ; ; If not found: ; A = 0 ; carry Set. INDEX: IF USE_INDEX = 1 LD A, (DE) ; Abort if string to find is too big CP (HL) INC A JR NC, IDXABORT DEC A ; Save length of string to find LD IXL, A LD B, 0 ; Put length of string to search in BC LD C, (HL) INC HL ; Advance pointers INC DE PUSH HL ; Save start of search string IDXRST: PUSH DE ; Save start of key string LD A, IXL ; Initialize matched characters counter LD IXH, A LD A, (DE) ; Get a character to match CPIR ; Look for it JR NZ, IDXNF ; Abort if not found IDXLOOP: DEC IXH ; Update counter and see if done JR Z, IDXFOUND INC DE ; Get next character in key string LD A, (DE) CPI ; See if it matches next char in master JR Z, IDXLOOP JP PO, IDXNF ; Abort if we ran out of characters POP DE ; If a mismatch, restart from the beginning JR IDXRST IDXNF: POP DE ; Clean stack POP HL IDXABORT: XOR A ; Report failure SCF RET IDXFOUND: POP DE POP BC ; BC = address of master XOR A ; Put size of key string in DE LD D, A LD E, IXL SBC HL, DE ; Find index SBC HL, BC LD A, L INC A RET ENDIF ; IN HL Address of string to be inserted ; DE Address of string to receive insertion ; C Index. Start of string is 0 ; OUT ; If successful: ; carry Reset ; HL Input DE ; If unsuccessful: ; carry Set. If new string length is > 255. ; ; Notes If index > string length, string is appended. ; Data after the string is destroyed. STRINSERT: IF USE_STRINSERT = 1 LD A, (DE) LD B, A INC A CP C JR NC, STRINSERT1 LD C, B STRINSERT1:DEC A ADD A, (HL) RET C LD (DE), A ; Update length PUSH DE ; Make room PUSH HL LD A, (HL) INC C LD H, 0 LD L, C ADD HL, DE LD D, H LD E, L PUSH AF ADD A, E LD E, A ADC A, D SUB E LD D, A POP AF LD B, 0 LD C, A PUSH HL LDIR POP DE ; Copy string over POP HL LD C, (HL) INC HL LDIR POP HL RET ENDIF ; IN HL Address of string. ; B Index of first character to delete. First character is 0. ; C Number of characters to kill. ; OUT ; If successful: ; carry Reset ; If unsuccessful: ; carry Set ; ; Notes If B > string length, then error. ; If B + C > string length, deletion ; stops at end of string. STRDELETE: IF USE_STRDELETE = 1 LD A, B ; See if index is too big CP (HL) CCF ; Flip for error RET C ADD A, C ; See if too many chars on chopping block CP (HL) JR C, STRDELETE1 INC B ; Set index as length LD (HL), B RET STRDELETE1:PUSH HL LD A, (HL) SUB C LD (HL), A INC HL LD E, C LD C, B LD B, 0 ADD HL, BC SUB C LD C, E LD D, H LD E, L ADD HL, BC LD C, A LDIR POP HL RET ENDIF ; IN HL Address of first string. ; DE Address of second string. ; OUT ; If successful: ; carry Reset ; If unsuccessful: ; carry Set ; ; Notes If new string lenght is > 255, error. ; HL is saved. CONCAT: IF USE_CONCAT = 1 LD A, (DE) ; Combine lengths ADD A, (HL) RET C LD C, (HL) LD (HL), A LD B, 0 INC C PUSH HL ADD HL, BC EX DE, HL LD C, (HL) INC HL LDIR POP HL RET ENDIF ; Utility: Convert character to upper case ; On entry: A = Character in either case ; On exit: A = Character in upper case ; BC DE HL IX IY I AF' BC' DE' HL' preserved ConvertCharToUCase: IF USE_CNVUPPER = 1 CP 'a' ;Character less than 'a'? RET C ;Yes, so finished CP 'z'+1 ;Character greater than 'z'? RET NC ;Yes, so finished SUB 'a'-'A' ;Convert case RET ENDIF ; ; Utility: Convert character to numberic value ; On entry: A = ASCII character (0-9 or A-F) ; On exit: If character is a valid hex digit: ; A = Numberic value (0 to 15) and Z flagged ; If character is not a valid hex digit: ; A = 0xFF and NZ flagged ; BC DE HL IX IY I AF' BC' DE' HL' preserved ; Interrupts not enabled ConvertCharToNumber: IF USE_CNVCHRTONUM = 1 CALL ConvertCharToUCase CP '0' ;Character < '0'? JR C,@Bad ;Yes, so no hex character CP '9'+1 ;Character <= '9'? JR C,@OK ;Yes, got hex character CP 'A' ;Character < 'A' JR C,@Bad ;Yes, so not hex character CP 'F'+1 ;Character <= 'F' JR C,@OK ;No, not hex ; Character is not a hex digit so return @Bad: LD A,0FFh ;Return status: not hex character OR A ; A = 0xFF and NZ flagged RET ; Character is a hex digit so adjust from ASCII to number @OK: SUB '0' ;Subtract '0' CP 00Ah ;Number < 10 ? JR C,@Finished ;Yes, so finished SUB 007h ;Adjust for 'A' to 'F' @Finished: CP A ;Return A = number (0 to 15) and Z flagged to RET ; indicate character is a valid hex digital ENDIF ; Utility: Is character numeric? ; On entry: A = ASCII character ; On exit: Carry flag set if character is numeric (0 to 9) ; A BC DE HL IX IY I AF' BC' DE' HL' preserved IsCharNumeric: IF USE_ISNUMERIC = 1 CP '0' ;Less than '0'? JR C,@Not2 ;Yes, so go return NOT numeric CP '9'+1 ;Less than or equal to '9'? RET C ;Yes, so numeric (C flagged) @Not2: OR A ;No, so NOT numeric (NC flagged) RET ENDIF ; Utility: Convert hexadecimal or decimal text to number ; On entry: DE = Pointer to start of ASCII string ; On exit: If valid number found: ; A = 0 and Z flagged ; HL = Number found ; If valid number not found: ; A != 0 and NZ flagged ; HL = Not specified ; DE = Not specified ; HL = Number ; BC DE IX IY I AF' BC' DE' HL' preserved ; Hexadecmal numbers can be prefixed with either "$" or "0x" ; Decimal numbers must be prefixed with "+" ; A number without a prefix is assumed to be hexadecimal ; Hexadecimal number without a prefix must start with "0" to "9" ; ... this is to stop the assembler getting confused between ; ... register names and constants which could be fixed by ; ... re-ordering the (dis)assebmer's instruction table ; Numbers can be terminated with ")", space, null or control code ; Negative numbers, preceded with "-", are not supported ; Text must be terminated with ')', space or control char. ConvertStringToNumber: IF USE_CNVSTRTONUM = 1 PUSH BC LD HL,0 ;Build result here LD A,(DE) ;Get character from string CP '+' ;Does string start with '+' ? JR Z,@Decimal ;Yes, so its decimal CP '$' ;Does string start with '$' ? JR Z,@Hdecimal ;Yes, so its hexadecimal CP 39 ;Does string start with apostrophe? JR Z,@Char ;Yes, so its a character CP '"' ;Does string start with '"' ? JR Z,@Char ;Yes, so its a character ; CALL IsCharNumeric ;Is first character '0' to '9' ? ; JR NC,@Failure ;No, so invalid number ; CALL IsCharHex ;Is first character hexadecimal ? ; JR NC,@Failure ;No, so invalid hex character CP '0' ;Is first character '0' ? ; JR NZ,@HexNext ;No, so default to hexadecimal JR NZ,@DecNext ;No, so default to decimal INC DE ;Point to next character in string LD A,(DE) ;Get character from string CALL ConvertCharToUCase CP 'X' ;Is second character 'x' ? JR NZ,@HexNext ;No, so must be default format ; JR NZ,@DecNext ;No, so must be default format ; Hexadecimal number... @Hdecimal: INC DE ;Point to next character in string @HexNext: LD A,(DE) ;Get character from string CP ')' ;Terminated with a bracket? JR Z,@Success ;yes, so success CP 32+1 ;Space or control character? JR C,@Success ;Yes, so successld hl CALL ConvertCharToNumber ;Convert character to number JR NZ,@Failure ;Return if failure (NZ flagged) INC DE ;Point to next character in string ADD HL,HL ;Current result = 16 * current result.. ADD HL,HL ADD HL,HL ADD HL,HL OR L ;Add new number (0 to 15).. LD L,A JR @HexNext ; Decimal number... @Decimal: INC DE ;Point to next character in string @DecNext: LD A,(DE) ;Get character from string CP ')' ;Terminated with a bracket? JR Z,@Success ;yes, so success CP 32+1 ;Space or control character? JR C,@Success ;Yes, so success CALL IsCharNumeric ;Is first character '0' to '9' ? JR NC,@Failure ;No, so invalid number CALL ConvertCharToNumber ;Convert character to number JR NZ,@Failure ;Return if failure (NZ flagged) INC DE ;Point to next character in string PUSH DE LD B,9 ;Current result = 10 * current result.. LD D,H LD E,L @DecLoop: ADD HL,DE ;Add result to itself 9 times DJNZ @DecLoop POP DE ADD A,L ;Add new number (0 to 15).. LD L,A JR NC,@DecNext INC H JR @DecNext ; Character... @Char: INC DE ;Point to next character in string LD A,(DE) ;Get ASCII character LD L,A ;Store ASCII value as result LD H,0 ; JR @Success ; Return result... @Success: POP BC XOR A ;Return success with A = 0 and Z flagged RET @Failure: POP BC LD A,0FFh ;Return failure with A != 0 OR A ; and NZ flagged RET ENDIF