# Disk 5: Strings and Substrings - [Part I: The Strings Collection](#part-i-the-strings-collection) - [Strings Components](#string-components) - [Strings Header File](#header-file) - [Strings Macros and Subroutines](#macros-and-subroutines) - [Whole Strings](#whole-string-macros) - [The SCMP Macro](#the-scmp-macro) - [The STRCOMP Subroutine](#the-strcomp-subroutine) - [The SCAT Macro](#the-scat-macro) - [The STRCAT Subroutine](#the-strcat-subroutine) - [The STRIM Macro](#the-strim-macro) - [The STRTRIM Subroutine](#the-strtrim-subroutine) - [The STRUP Macro](#the-strup-macro) - [The STRUPPER Subroutine](#the-strupper-subroutine) - [The SLO Macro](#the-slo-macro) - [The STRLOWER Subroutine](#the-strlower-subroutine) - [The SREV Macro](#the-srev-macro) - [The STRREV Subroutine](#the-strrev-subroutine) - [The SCAP Macro](#the-scap-macro) - [The STRCAP Subroutine](#the-strcap-subroutine) - [Substrings](#substring-macros) - [The SPOS Macro](#the-spos-macro) - [The SUBPOS Subroutine](#the-subpos-subroutine) - [The SCPY Macro](#the-scpy-macro) - [The SUBCOPY Subroutine](#the-subcopy-subroutine) - [The SDEL Macro](#the-sdel-macro) - [The SUBDEL Subroutine](#the-subdel-subroutine) - [The SINS Macro](#the-sins-macro) - [The SUBINS Subroutine](#the-subins-subroutine) - [The STOK Macro](#the-stok-macro) - [The SUBTOK Subroutine](#the-subtok-subroutine) - [The SCNT Macro](#the-scnt-macro) - [The SUBCHARCNT Subroutine](#the-subcharcnt-subroutine) - [Part II: String Collection Demos](#part-ii-string-and-substring-demonstrations) - Whole Strings Demo - Substrings Demo --- ## Part I: The Strings Collection The fifth disk of the AppleIIAsm library is dedicated to manipulating strings, with a substantial portion of the macros and subroutines dealing with substring functions. Since roughly half of these deal with whole strings and half deal with substrings, the strings collection is further divided as such in terms of both macros and demos: whole string functions and substring functions. This collection includes macros and substrings for the following: - Finding a substring position - Copying a substring - Deleting a substring - Inserting a substring - Finding a tokenized substring - Counting the number of tokens in a string - Comparing strings - Concatenating strings - Trimming strings - String conversion to uppercase - String conversion to lowercase - String reversal - Sentence-based capitalization --- ## Strings Components The Strings collection contains the following components: - A header file that includes a number of hooks and vectors to be used by the macros and subroutines in the collection. - Macros and subroutines used to manipulate strings and substrings in variety of ways. These can be further paired for greater functionality, for instance by using `SDEL` and `SINS` to write a substring replacing routine. Currently, the macros are divided into two files: MAC.STRINGS.ASM for whole string operations and MAC.SUBSTRINGS.ASM for substring operations. - Demonstrations of all of the macros in the strings collection. This is divided into two files, the DEMO.STRINGS.ASM file and the DEMO.SUBSTRINGS.ASM file. --- ## Header File | Condition | Value | | ------------- | ------------------------------------------------------------ | | Name | File: HEAD.STRINGS.ASM | | Type | Header File | | Author | Nathan Riggs | | Last Revision | 03-APR-2021 | | Assembler | Merlin 8 Pro | | OS | Apple DOS 3.3 | | Purpose | Provide appropriate hooks and routines for the Strings collection | | Dependencies | none | | Bytes | 0 | | Notes | Repeatedly used subroutines by the library may be placed here in the future | | See Also | none | --- *DETAILS* Currently, the Strings Collection header file only includes a single vector, which serves as an alternative entry point to `COUT`. `LISTING 5.00: HEAD.STRINGS.ASM Source` ```assembly * *``````````````````````````````* * HOOKS.STRINGS * * * * THIS FILE CONTAINS ALL OF * * THE HOOKS REQUIRED BY THE * * STRING LIBRARY. * * * * AUTHOR: NATHAN RIGGS * * CONTACT: NATHAN.RIGGS@ * * OUTLOOK.COM * * * * DATE: 19-SEP-2019 * * ASSEMBLER: MERLIN 8 PRO * * OS: DOS 3.3 * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCOUT1 EQU $FDF0 * ``` --- ### Macros and Subroutines The Strings Collection is further divided into two basic sections: functions for manipulating whole strings and those used for manipulating substrings. By combining the various macros provided, more complex (and more contemporary) routines may be created, such as a substring replace function, a substring search function, and so on. --- ### Whole String Macros The macros contained here are dedicated to operations on whole strings. Technically, of course, all strings are whole strings; what is meant by "whole string operation" here is that the entire string being operated on is passed back after the macro is called, albeit transformed in some way. `LISTING 5.10: MAC.STRINGS.ASM Header Source` ```assembly * *``````````````````````````````* * MAC.STRINGS * * * * THIS FILE CONTAINS ALL OF * * THE MACROS RELATED TO STRING * * MANIPULATION. * * * * AUTHOR: NATHAN RIGGS * * CONTACT: NATHAN.RIGGS@ * * OUTLOOK.COM * * * * DATE: 11-APR-2021 * * ASSEMBLER: MERLIN 8 PRO * * OS: DOS 3.3 * * * * SUBROUTINE FILES USED * * * * SUB.STRCAT.ASM * * SUB.STRCOMP.ASM * * SUB.STRTRIM.ASM * * SUB.STRUPPER.ASM * * SUB.STRLOWER.ASM * * SUB.STRREV.ASM * * SUB.STRCAP.ASM * * * * LIST OF MACROS * * * * SCMP : STRING COMPARE * * SCAT : STRING CONCATENATE * * STRIM: STRING TRIM * * SUP : STRING UPPERCASE * * SLO : STRING LOWERCASE * * SREV : STRING REVERSE * * SCAP : STRING CAPITALIZATION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ``` --- ### THE SCMP MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SCMP` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Compare two strings | | Input | ]1 = First string to compare
]2 = Second string to compare | | Output | none | | Dependencies | `SUB.STRCOMP.ASM` | | Flags Destroyed | NZCV | | Cycles | 110+ | | Bytes | 67 bytes | | Notes | None | | See Also | none | --- *DETAILS* The `SCMP` macro accepts two strings as input and compares them, returning the results via the status register as a regular `CMP` instruction would do. In particular, the zero flag and the carry flag are set or unset based on the comparison of the strings as follows: - If the strings match, then the zero flag is set to one - If the strings do not match, then the zero flag is set to zero - If the strings do match up to the length of the shortest string, then a further comparison is done to test the lengths. Then: - The carry flag is set to zero if the first string length is less than the second string length - The carry flag is set to one if the first string length is greater than or equal to the length of the second string `LISTING 5.11: The SCMP Macro Source` ```assembly * *``````````````````````````````* * SCMP (NATHAN RIGGS) * * * * COMPARES TWO STRINGS AND * * CHANGES THE ZERO FLAG TO 1 * * IF THE STRINGS ARE EQUAL. IF * * UNEQUAL, THE MACRO THEN * * COMPARES THE LENGTHS; IF THE * * FIRST IS LESS THAN SECOND, * * THE CARRY FLAG IS SET TO 0. * * OTHERWISE, IT IS SET TO 1. * * * * PARAMETERS * * * * ]1 = 1ST STRING TO COMPARE * * ]2 = 2ND STRING TO COMPARE * * * * CYCLES: 110+ * * SIZE: 67 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCMP MAC STY SCRATCH ; {3C2B} BACKUP .Y REGISTER _MSTR ]1;WPAR1 ; {15C14B} _MSTR ]2;WPAR2 ; {15C14B} JSR STRCMP ; {74C26B} LDY SCRATCH ; {3C2B} RELOAD .Y <<< * ``` --- ### THE STRCOMP SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------ | | Name | `STRCOMP` | | Type | Subroutine | | File | `SUB.STRCOMP.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Compare two strings | | Input | WPAR1 = First string
WPAR2 = Second strings | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 68+ | | Bytes | 32 | | Notes | none | | See Also | `SCMP` | --- *DETAILS* The `STRCOMP` subroutine is provided the addresses of two strings with a preceding length-byte, then runs a comparison on them. The results are passed back via the Carry and Zero flags of the status register, as such: - If the strings match, then the zero flag is set to one - If the strings do not match, then the zero flag is set to zero - If the strings do match up to the length of the shortest string, then a further comparison is done to test the lengths. Then: - The carry flag is set to zero if the first string length is less than the second string length - The carry flag is set to one if the first string length is greater than or equal to the length of the second string `LISTING 5.12: The STRCOMP Subroutine Source` ```assembly *``````````````````````````````* * STRCMP (NATHAN RIGGS) * * * * COMPARES A STRING TO ANOTHER * * STRING AND SETS THE FLAGS * * ACCORDINGLY: * * * * Z = 1 IF STRINGS MATCH * * Z = 0 IF STRINGS DON'T MATCH * * * * IF THE STRINGS MATCH UP TO * * THE LENGTH OF THE SHORTEST * * STRING, THE STRING LENGTHS * * ARE THEN COMPARED AND THE * * CARRY FLAG IS SET AS SUCH: * * * * C = 0 IF 1ST STRING < 2ND * * C = 1 IF 1ST STRING >= 2ND * * * * INPUT: * * * * WPAR1 = 1ST STRING ADDRESS * * WPAR2 = 2ND STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 68+ * * SIZE: 32 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR1 EQU WPAR1 ; ZP POINTER TO 1ST STRING ]STR2 EQU WPAR2 ; ZP POINTER TO 2ND STRING * STRCMP * LDY #0 ; {3C2B} RESET .Y COUNTER LDA (]STR1),Y ; {5C2B} GET LENGTH OF 1ST STRING CMP (]STR2),Y ; {5C2B} IF STR1 LENGTH < STR2 LENGTH BCC :BEGCMP ; {3C2B} THEN BEGIN COMPARISON; ELSE LDA (]STR2),Y ; {5C2B} USE STR2 LENGTH INSTEAD :BEGCMP TAX ; {2C1B} X IS LENGTH OF SHORTER STRING BEQ :TSTLEN ; {3C2B} IF LENGTH IS 0, TEST LENGTH LDY #1 ; {3C2B} ELSE SET .Y TO 1ST CHAR :CMPLP LDA (]STR1),Y ; {5C2B} GET INDEXED CHAR OF 1ST STRING CMP (]STR2),Y ; {5C2B} COMPARE TO INDEXED CHAR OF 2ND BNE :EXIT ; {3C2B} EXIT IF THE CHARS ARE NOT EQUAL ; Z,C WILL BE PROPERLY SET INY ; {2C1B} INCREASE CHARACTER INDEX DEX ; {2C1B} DECREMENT COUNTER BNE :CMPLP ; {3C2B} CONT UNTIL ALL CHARS CHECKED :TSTLEN LDY #0 ; {3C2B} NOW COMPARE LENGTHS LDA (]STR1),Y ; {5C2B} GET LENGTH OF 1ST STRING CMP (]STR2),Y ; {5C2B} SET OR CLEAR THE FLAGS :EXIT RTS ; {6C1B} ``` --- ### THE SCAT MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------ | | Name | `SCAT` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Concatenate one string to another | | Input | ]1 = First string
]2 = Second string | | Output | none | | Dependencies | `SUB.STRCAT.ASM` | | Flags Destroyed | NZCV | | Cycles | 170+ | | Bytes | 113 bytes | | Notes | None | | See Also | `STRCAT` | --- *DETAILS* The `STRCAT` macro concatenates two strings, with the string at the second given address attached to the end of the string at the first given address. Like with all of the whole string macros, a new copy of the concatenated strings is passed back via **RETURN**, with its length in **RETLEN**. The length is also available in the **.A ** register. `LISTING 5.13: The STRCAT Macro Source` ```assembly * *``````````````````````````````* * SCAT (NATHAN RIGGS) * * * * CONCATENATE TWO STRINGS * * * * PARAMETERS * * * * ]1 = FIRST STRING * * ]2 = SECOND STRING * * * * CYCLES: 170+ * * SIZE: 113 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCAT MAC STY SCRATCH ; {3C2B} BACKUP .Y _MSTR ]1;WPAR1 ; {15C14B} _MSTR ]2;WPAR2 ; {15C14B} JSR STRCAT ; {134C81B} LDY SCRATCH ; {3C2B} RESTORE .Y <<< * ``` --- ### THE STRCAT SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ----------------------------------------------- | | Name | `STRCAT` | | Type | Subroutine | | File | `SUB.STRCAT.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Compare two strings | | Input | WPAR1 = First string
WPAR2 = Second string | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 128+ | | Bytes | 78 | | Notes | none | | See Also | `SCAT` | --- *DETAILS* The `STRCAT` subroutine appends (or concatenates) a given second string to a first string. The resulting new string is stored in **RETURN**, with the length stored in **RETLEN.** `LISTING 5.14: The STRCAT Subroutine Source` ```assembly *``````````````````````````````* * STRCAT (NATHAN RIGGS) * * * * CONCATENATE TWO STRINGS AND * * STORE THE NEW STRING IN * * RETURN, WITH THE LENGTH BYTE * * AT RETLEN. * * * * NOTE THAT THE WHOLE STRING * * IS ACTUALLY PLACED IN RETLEN * * TO ACCOUNT FOR THE LENGTH * * BYTE THAT PRECEDES IT. * * * * INPUT: * * * * WPAR1 = 1ST STRING ADDRESS * * WPAR2 = 2ND STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 128+ * * SIZE: 78 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]S1LEN EQU VARTAB+1 ; FIRST STRING LENGTH ]S2LEN EQU VARTAB+3 ; SECOND STRING LENGTH ]INDEX EQU WPAR3 ; ADDRESS TO PLACE 2ND STRING ]STR2 EQU WPAR2 ; POINTER TO 2ND STRING ]STR1 EQU WPAR1 ; POINTER TO 1ST STRING * STRCAT * LDY #0 ; {3C2B} CLEAR INDEX POINTER LDA (]STR1),Y ; {5C2B} GET LENGTH OF 1ST STRING STA ]S1LEN ; {4C3B} STORE IN 1ST STRING LENGTH LDA (]STR2),Y ; {5C2B} GET LENGTH OF 2ND STRING STA ]S2LEN ; {4C3B} STORE 2ND STRING LENGTH * ** DETERMINE NUMBER OF CHAR * LDA ]S2LEN ; {4C3B} GET 2ND STRING LENGTH CLC ; {2C1B} CLEAR CARRY ADC ]S1LEN ; {4C3B} ADD TO LENGTH OF 1ST STRING STA RETLEN ; {4C3B} SAVE SUM OF TWO LENGTHS BCC :DOCAT ; {3C2B} NO OVERFLOW, JUST CONCATENATE LDA #255 ; {3C2B} OTHERWISE, 255 IS MAX STA RETLEN ; {4C3B} * :DOCAT * LDY #0 ; {4C3B} OFFSET INDEX BY 1 :CAT1 INY ; {2C1B} LDA (]STR1),Y ; {5C2B} LOAD 1ST STRING INDEXED CHAR STA RETLEN,Y ; {5C2B} STORE IN RETURN AT SAME INDEX CPY ]S1LEN ; {4C3B} IF .Y < 1ST STRING LENGTH BNE :CAT1 ; {3C2B} THEN LOOP UNTIL FALSE * TYA ; {2C1B} TRANSFER COUNTER TO .A CLC ; {2C1B} CLEAR CARRY ADC #RETLEN+1 ; {4C3B} OF NEW ADDRESS STA ]INDEX+1 ; {4C3B} AND STORE HIGH BYTE CLC ; {2C1B} RESET CARRY LDY #0 ; {3C2B} * :CAT2 INY ; {2C1B} LDA (]STR2),Y ; {5C2B} LOAD 2ND STRING INDEXED CHAR STA (]INDEX),Y ; {5C2B} STORE AT NEW ADDRESS CPY RETLEN ; {4C3B} IF .Y < 2ND STRING LENGTH BEQ :EXIT ; {3C2B} BNE :CAT2 ; {3C2B} LOOP UNTIL FALSE :EXIT LDA RETLEN ; {4C3B} RETURN NEW LENGTH IN .A RTS ; {6C2B} ``` --- ### THE STRIM MACRO _SUMMARY_ | Condition | Value | | --------------- | -------------------------------------------------------- | | Name | `STRIM` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Trim a token from the beginning and
end of a string | | Input | ]1 = String
]2 = Token | | Output | none | | Dependencies | `SUB.STRTRIM.ASM` | | Flags Destroyed | NZCV | | Cycles | 123+ | | Bytes | 80 | | Notes | None | | See Also | `STRTRIM` | --- *DETAILS* The `STRIM` macro trims a single character (like a space) from the beginning and end of a string, if said character is present on either end. The new string is stored in **RETURN** with its length in **RETLEN**, which is also passed back via the **.A** register. `LISTING 5.15: The STRIM Macro Source` ```assembly * *``````````````````````````````* * STRIM (NATHAN RIGGS) * * * * CONCATENATE TWO STRINGS * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * ]2 = DELIMITER * * * * CYCLES: 123+ * * SIZE: 80 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * STRIM MAC _MSTR ]1;WPAR1 ; {15C14B} LDA ]2 ; {4C3B} STA WPAR2 ; {3C2B} JSR STRTRIM ; {101C61B} <<< * ``` --- ### THE STRTRIM SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | --------------------------------- | | Name | `STRTRIM` | | Type | Subroutine | | File | `SUB.STRTRIM.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Compare two strings | | Input | WPAR1 = String
WPAR2 = Token | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 95+ | | Bytes | 58 | | Notes | none | | See Also | `STRIM` | --- *DETAILS* The `STRTRIM` subroutine trims a specified token from the beginning and end of a string at a given address. Once executed, the new string is held in **RETURN**, with its length held in **RETLEN**. This length is also passed back via the **.A** register. `LISTING 5.16: The STRTRIM Subroutine Source` ```assembly *``````````````````````````````* * STRTRIM (NATHAN RIGGS) * * * * STRTRIM CUTS THE SPECIFIED * * CHARACTER FROM THE FRONT AND * * END OF THE GIVEN STRING, IF * * IT EXISTS. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * WPAR2 = DELIMITER * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 95+ * * SIZE: 58 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]DELIM EQU WPAR2 ; DELIMITER ]RET EQU WPAR3 ; RETURN AREA ]LEN EQU BPAR1 ; STRING LENGTH * STRTRIM LDY #0 ; {3C2B} RESET .Y COUNTER LDA (]STR),Y ; {5C3B} GET STRING LENGTH STA ]LEN ; {3C2B} STORE IN TEMP VARIABLE TYA ; {2C1B} RESET .A TAX ; {2C1B} RESET .X :FIRST LDY #1 ; {3C2B} LOAD FIRST CHAR OF STRING LDA (]STR),Y ; {5C3B} CMP ]DELIM ; {3C2B} COMPARE TO THE DELIMITER BNE :NOFIRST ; {3C2B} IF NO MATCH, SKIP TO :NOFIRST LDX #255 ; {3C2B} IF MATCHED, SET X TO -1 LDY #1 ; {3C2B} AND SET Y TO 1 JMP :COPY ; {3C3B} JUMP OVER TO COPYING :NOFIRST LDX #255 ; {3C2B} SET .X TO -1 LDY #0 ; {3C2B} SET .Y TO 0 :COPY INY ; {2C1B} INCREASE .Y INX ; {2C1B} INCREASAE .X LDA (]STR),Y ; {5C3B} LOAD CHAR FROM STRING AT .Y STA RETURN,X ; {5C3B} STORE IN RETURN AT .X CPY ]LEN ; {3C2B} IF .Y != STRING LENGTH BNE :COPY ; {3C2B} THEN RELOOP COPYING :LAST LDY ]LEN ; {3C2B} LOAD LENGTH INTO .Y LDA (]STR),Y ; {5C3B} LOAD LAST CHAR OF STRING CMP ]DELIM ; {3C2B} IF NOT EQUAL TO DELIMITER BNE :EXIT ; {3C2B} THEN GOTO EXIT DEC ]LEN ; {2C1B} OTHERWISE, DECREASE LENGTH :EXIT LDY ]LEN ; {3C2B} LOAD (POSSIBLY) ALTERED LENGTH STY RETLEN ; {4C3B} STORE IN RETLEN TYA ; {2C1B} PASS LENGTH IN .A REGISTER RTS ; {6C1B} ``` --- ### THE STRUP MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------- | | Name | `STRUP` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Turn a string into its
uppercase equivalent. | | Input | ]1 = String | | Output | none | | Dependencies | `SUB.STRUPPER.ASM` | | Flags Destroyed | NZCV | | Cycles | 114+ | | Bytes | 64 | | Notes | None | | See Also | `SUB.STRUPPER.ASM` | --- *DETAILS* The `STRUP` macro accepts a string or address to a string and transforms it into a string that represents its uppercase equivalent. This new string is passed back in **RETURN**, with the length held in **RETLEN**. The length is also available in the **.A** register. `LISTING 5.17: The STRUP Macro Source` ```assembly * *``````````````````````````````* * STRUP (NATHAN RIGGS) * * * * CONVERTS A STRING TO ITS * * UPPERCASE EQUIVALENT. * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * * * CYCLES: 114+ * * SIZE: 64 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * STRUP MAC _MSTR ]1;WPAR1 ; {15C14B} JSR STRUPPER ; {99C50B} <<< * ``` --- ### THE STRUPPER SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ----------------------------- | | Name | `STRUPPER` | | Type | Subroutine | | File | `SUB.STRUPPER.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Convert a string to uppercase | | Input | WPAR1 = String address | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 93+ | | Bytes | 47 | | Notes | none | | See Also | `STRUP` | --- *DETAILS* The `STRUPPER` subroutine accepts a string address and passes back a string via **RETURN** that is an all-uppercase equivalent of the string, with its length stored in both **RETLEN** and in the **.A** register. `LISTING 5.18: The STRUPPER Subroutine Source` ```assembly *``````````````````````````````* * STRUPPER (NATHAN RIGGS) * * * * THIS SUBROUTINE ACCEPTS A * * STRING AND PASSES BACK A NEW * * STRING THAT IS ITS UPPERCASE * * EQUIVALENT. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 93+ * * SIZE: 47 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]LEN EQU BPAR1 ; STRING LENGTH ]RET EQU WPAR2 ; REUTURN ADDRESS * STRUPPER * LDA #0 ; {3C2B} RESET .A REGISTER TAX ; {2C1B} RESET .X TAY ; {2C1B} RESET .Y LDA (]STR),Y ; {5C2B} GET THE STRING LENGTH STA ]LEN ; {3C2B} AND STORE IN ]LEN INC ]LEN ; {5C2B} TEMPORARILY INCREASE LENGTH LDY #255 ; {3C2B} COUNTER = -1 :COPYLP INY ; {2C1B} INCREASE INDEX COUNTER LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING STA RETLEN,Y ; {5C2B} STORE IN RETURN AREA AT INDEX CPY ]LEN ; {3C2B} IF .Y != STRING LENGTH BNE :COPYLP ; {3C2B} KEEP LOOPING UNTIL ALL COPIED * LDY #0 ; {3C2B} RESET INDEX COUNTER DEC ]LEN ; {5C2B} LENGTH BACK TO NORMAL :MAINLP INY ; {2C1B} INCREASE INDEX COUNTER LDA RETLEN,Y ; {5C2B} LOAD CHARACTER FROM RETURN CMP #225 ; {3C2B} IS IT < LOWERCASE A? BCC :RELOOP ; {3C2B} IF SO, LOOP TO NEXT CHAR CMP #251 ; {3C2B} IS IT >= LOWERCASE Z? BCS :RELOOP ; {3C2B} IF SO, LOOP TO NEXT CHAR SEC ; {2C1B} ELSE, WE FOUND A LOWERCASE LETTER SBC #32 ; {3C2B} SO SUBTRACT 32 TO GET UPPERCASE STA RETLEN,Y ; {5C2B} AND REPLACE THE LETTER IN RETURN :RELOOP CPY ]LEN ; {3C2B} NOW CHECK IF Y = LENGTH BNE :MAINLP ; {3C2B} AND IF NOT, CONTINUE LOOP :EXIT TYA ; {2C1B} SEND BACK LENGTH IN .A RTS ; {6C1B} ``` --- ### THE SLO MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------- | | Name | `SLO` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Turn a string into its
lowercase equivalent. | | Input | ]1 = String | | Output | none | | Dependencies | `SUB.STRLOWER.ASM` | | Flags Destroyed | NZCV | | Cycles | 113+ | | Bytes | 64 | | Notes | None | | See Also | `SUB.STRLOWER.ASM` | --- *DETAILS* The `STRLO` macro accepts a string or a string address and returns the lowercase equivalent of the string in **RETURN**, with its length passed back in both **RETLEN** and the **.A** register. `LISTING 5.19: The SLO Macro Source` ```assembly * *``````````````````````````````* * STRLO (NATHAN RIGGS) * * * * CONVERTS A STRING TO ITS * * LOWERCASE EQUIVALENT. * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * * * CYCLES: 113+ * * SIZE: 64 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * STRLO MAC _MSTR ]1;WPAR1 ; {15C14B} JSR STRLOWER ; {98C50B} <<< * ``` --- ### THE STRLOWER SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ----------------------------- | | Name | `STRLOWER` | | Type | Subroutine | | File | `SUB.STRLOWER.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Convert a string to lowercase | | Input | WPAR1 = String address | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 92+ | | Bytes | 47 | | Notes | none | | See Also | `SLO` | --- *DETAILS* The `STRLOWER` subroutine accepts an address that holds a string and converts the string to its lowercase equivalent, storing the new string in **RETURN** with its preceding length-byte in **RETLEN**. The length is also passed back via the **.A** register. `LISTING 5.20: The STRLOWER Subroutine Source` ```assembly *``````````````````````````````* * STRLOWER (NATHAN RIGGS) * * * * THIS SUBROUTINE ACCEPTS A * * STRING AND PASSES BACK A NEW * * STRING THAT IS ITS LOWERCASE * * EQUIVALENT. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 92+ * * SIZE: 47 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]LEN EQU BPAR1 ; STRING LENGTH ]RET EQU WPAR2 ; REUTURN ADDRESS * STRLOWER * LDA #0 ; {3C2B} RESET .A REGISTER TAX ; {2C1B} RESET .X TAY ; {2C1B} RESET .Y LDA (]STR),Y ; {5C2B} GET THE STRING LENGTH STA ]LEN ; {3C2B} AND STORE IN ]LEN INC ]LEN ; {5C2B} TEMPORARILY INCREASE LENGTH LDY #255 ; {3C2B} COUNTER = -1 :COPYLP INY ; {2C1B} INCREASE INDEX COUNTER LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING STA RETLEN,Y ; {5C2B} STORE IN RETURN AREA AT INDEX CPY ]LEN ; {3C2B} IF .Y != STRING LENGTH BNE :COPYLP ; {3C2B} KEEP LOOPING UNTIL ALL COPIED * LDY #0 ; {3C2B} RESET INDEX COUNTER DEC ]LEN ; {5C2B} LENGTH BACK TO NORMAL :MAINLP INY ; {2C1B} INCREASE INDEX COUNTER LDA RETLEN,Y ; {5C2B} LOAD CHARACTER FROM RETURN CMP #193 ; {3C2B} IS IT < UPPERCASE A? BCC :RELOOP ; {3C2B} IF SO, LOOP TO NEXT CHAR CMP #219 ; {3C2B} IS IT >= UPPERCASE Z? BCS :RELOOP ; {3C2B} IF SO, LOOP TO NEXT CHAR CLC ; {2C1B} ELSE, FOUND AN UPPERCASE LETTER ADC #32 ; {3C2B} SO ADD 32 TO GET LOWERCASE STA RETLEN,Y ; {5C2B} AND REPLACE THE LETTER IN RETURN :RELOOP CPY ]LEN ; {3C2B} NOW CHECK IF Y = LENGTH BNE :MAINLP ; {3C2B} AND IF NOT, CONTINUE LOOP :EXIT TYA ; {2C1B} SEND BACK LENGTH IN .A RTS ; {6C1B} ``` --- ### THE SREV MACRO _SUMMARY_ | Condition | Value | | --------------- | ----------------- | | Name | `SREV` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Reverse a string. | | Input | ]1 = String | | Output | none | | Dependencies | `SUB.STRREV.ASM` | | Flags Destroyed | NZCV | | Cycles | 70+ | | Bytes | 41 | | Notes | None | | See Also | `SUB.STRREV.ASM` | --- *DETAILS* The `SREV` macro accepts a string or a string's address and passes back a copy of that string in reverse. This new string is passed back via **RETURN**, with the length-byte held in **RETLEN**. The length can also be retrieved in the **.A** register. `LISTING 5.21: The SREV Macro Source` ```assembly * *``````````````````````````````* * SREV (NATHAN RIGGS) * * * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * * * CYCLES: 70+ * * SIZE: 41 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SREV MAC _MSTR ]1;WPAR1 ; {14C13B} JSR STRREV ; {56C28B} <<< ``` --- ### THE STRREV SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ---------------------- | | Name | `STRREV` | | Type | Subroutine | | File | `SUB.STRREV.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | reverse a string | | Input | WPAR1 = String address | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 50+ | | Bytes | 25 | | Notes | none | | See Also | `SREV` | --- *DETAILS* The `STRREV` subroutine accepts a string address and then passes back a copy of the string reversed in **RETURN.** The length is held in both **RETLEN** and in the **.A** register. `LISTING 5.22: The STRREV Subroutine Source` ```assembly *``````````````````````````````* * STRREV (NATHAN RIGGS) * * * * REVERSE A STRING. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 50+ * * SIZE: 25 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]REV EQU RETLEN ; REVERSE STRING ADDRESS ]LEN EQU BPAR1 ; STRING LENGTH * STRREV LDY #0 ; {3C2B} CLEAR .Y REGISTER LDX #0 ; {3C2B} CLEAR .X REGISTER LDA (]STR),Y ; {5C2B} LOAD STRING LENGTH STA ]LEN ; {3C2B} AND STORE IN ]LEN TAY ; {2C1B} TRANSFER LENGTH TO .Y INDEX INY ; {2C1B} INCREASE BY ONE * :LP1 INX ; {2C1B} INCREASE .X COUNTER DEY ; {2C1B} DECREASE .Y COUNTER LDA (]STR),Y ; {5C2B} LOAD CHAR FROM BACK OF STRING STA ]REV,X ; {5C2B} STORE IN REVERSE ADDRESS CPX ]LEN ; {3C2B} COMPARE .X COUNTER TO LENGTH BNE :LP1 ; {3C2B} IF !=, THEN RELOOP :EXIT TXA ; {2C1B} MOVE LENGTH TO .A REGISTER STX RETLEN ; {4C3B} ALSO STORE IN RETLEN RTS ; {6C1B} ``` --- ### THE SCAP MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SCAP` | | Type | Macro | | File | `MAC.STRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Capitalize characters in a string
based on punctuation. | | Input | ]1 = String | | Output | none | | Dependencies | `SUB.STRCAP.ASM` | | Flags Destroyed | NZCV | | Cycles | 192+ | | Bytes | 117 | | Notes | None | | See Also | `SUB.STRCAP.ASM` | --- *DETAILS* The `SCAP` macro accepts a string or string address and returns a copy of the string with the appropriate letters capitalized, including the first letter of the string. This can be used in conjunction with the `SLO` macro to get a properly formatted copy of a string that was previously in all uppercase. The new string is passed back via **RETURN** with its length in **RETLEN.** `LISTING 5.23: The SCAP Macro Source` ```assembly * *``````````````````````````````* * SCAP (NATHAN RIGGS) * * * * CAPITALIZES THE APPROPRIATE * * CHARACTERS IN A LOWERCASE * * STRING. * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * * * CYCLES: 192+ * * SIZE: 117 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCAP MAC _MSTR ]1;WPAR1 ; {15C14B} JSR STRCAP ; {177C103B} <<< * ``` --- ### THE STRCAP SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------ | | Name | `STRCAP` | | Type | Subroutine | | File | `SUB.STRCAP.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | capitalize the beginning of
sentences in a string | | Input | WPAR1 = String | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 171+ | | Bytes | 100 | | Notes | none | | See Also | `SCAP` | --- *DETAILS* The `STRCAP` subroutine accepts an address that points to a string, then creates a copy of that string in **RETURN** with the first letter of every sentence capitalized, including the first letter of the string. The length is passed back via **RETLEN** as well as in the **.A ** register. `LISTING 5.24: The STRCAP Subroutine Source` ```assembly *``````````````````````````````* * STRCAP (NATHAN RIGGS) * * * * THIS SUBROUTINE TAKES A * * STRING OF LOWERCASE LETTERS * * AND CAPITALIZES LETTERS WHEN * * IT IS APPROPRIATE--AFTER A * * PERIOD, ETC. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 171+ * * SIZE: 100 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]LEN EQU BPAR1 ; STRING LENGTH ]FLAG EQU WPAR2 ; CAPITALIZATION FLAG * STRCAP * LDA #0 ; {3C2B} RESET .A REGISTER TAX ; {2C1B} RESET .X TAY ; {2C1B} RESET .Y STA ]FLAG ; {3C2B} LDA (]STR),Y ; {5C2B} GET THE STRING LENGTH STA ]LEN ; {3C2B} AND STORE IN ]LEN INC ]LEN ; {5C2B} TEMPORARILY INCREASE LENGTH LDY #255 ; {3C2B} COUNTER = -1 :COPYLP INY ; {2C1B} INCREASE INDEX COUNTER LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING STA RETLEN,Y ; {5C2B} STORE IN RETURN AREA AT INDEX CPY ]LEN ; {3C2B} IF .Y != STRING LENGTH BNE :COPYLP ; {3C2B} KEEP LOOPING UNTIL ALL COPIED * LDY #0 ; {3C2B} RESET INDEX COUNTER DEC ]LEN ; {5C2B} LENGTH BACK TO NORMAL :MAINLP INY ; {2C1B} INCREASE INDEX COUNTER LDA RETLEN,Y ; {5C2B} GET CHARACTER FROM STRING CMP #"." ; {3C2B} IS IT A PERIOD? BEQ :SETFLAG ; {3C2B} IF SO, SET CAPITAL FLAG CMP #"?" ; {3C2B} IS IT A QUESTION MARK? BEQ :SETFLAG ; {3C2B} IF SO, SET CAPITAL FLAG CMP #"!" ; {3C2B} IS IT AN EXCLAMATION MARK? BEQ :SETFLAG ; {3C2B} IF SO, SET CAPITAL FLAG * CMP #225 ; {3C2B} COMPARE TO LOWERCASE A BCC :RELOOP ; {3C2B} IF ASCII < THAT, DO NOT REPLACE CMP #251 ; {3C2B} COMPARE TO LOWERCASE Z BCS :RELOOP ; {3C2B} IF ASCII >= THAT, DON'T REPLACE * LDX ]FLAG ; {3C2B} LOAD CAPITALIZATION FLAG CPX #1 ; {3C2B} COMPARE TO #1 (SET) BNE :RELOOP ; {3C2B} IF NOT SET, LOOP TO NEXT CHAR SEC ; {2C1B} IF SET, THEN SET CARRY SBC #32 ; {3C2B} SUBTRACT #32 ASCII STA RETLEN,Y ; {5C2B} STORE OVER TOP LOWERCASE LDX #0 ; {3C2B} CLEAR .X REGISTER STX ]FLAG ; {3C2B} AND CLEAR CAPITALIZATION FLAG :RELOOP CPY ]LEN ; {3C2B} NOW CHECK IF Y = LENGTH BNE :MAINLP ; {3C2B} AND IF NOT, CONTINUE LOOP JMP :EXIT ; {3C3B} IF SO, JUMP TO EXIT :SETFLAG LDX #1 ; {3C2B} LOAD #1 IN .X REGISTER STX ]FLAG ; {3C2B} STORE IN FLAG (MEANING IT IS SET) JMP :RELOOP ; {3C3B} LOOP TO NEXT CHARACTER :EXIT LDY #1 ; {3C2B} LOAD FIRST LETTER OF STRING LDA RETLEN,Y ; {5C2B} CMP #225 ; {3C2B} COMPARE TO LOWERCASE A BCC :EXIT2 ; {3C2B} IF < LC A, EXIT FOR REAL CMP #251 ; {3C2B} COMPARE TO LOWERCASE Z BCS :EXIT2 ; {3C2B} IF >= LC Z, EXIT FOR REAL SEC ; {2C1B} OTHERWISE, SET THE CARRY SBC #32 ; {3C2B} AND SUBTRACT 32 FROM ASCII STA RETLEN,Y ; {5C2B} AND STORE OVER TOP FIRST CHAR :EXIT2 LDA ]LEN ; {3C2B} SEND BACK LENGTH IN .A RTS ; {6C1B} ``` --- ### Substring Macros The macros contained here are dedicated to operations on substrings, which we define as strings pulled from parts of another string. Put together, the macros and subroutines available here should be enough to create more complicated routines for substring manipulation, such as searching and replacing, counting substring repetitions, and so on. `LISTING 5.30: MAC.SUBSTRINGS.ASM Header Source` ```assembly *``````````````````````````````* * MAC.SUBSTRINGS.ASM * * * * THIS FILE CONTAINS ALL OF * * THE MACROS RELATED TO * * SUBSTRING MANIPULATION. * * * * AUTHOR: NATHAN RIGGS * * CONTACT: NATHAN.RIGGS@ * * OUTLOOK.COM * * * * DATE: 12-APR-2021 * * ASSEMBLER: MERLIN 8 PRO * * OS: DOS 3.3 * * * * SUBROUTINE FILES USED * * * * SUB.SUBCOPY.ASM * * SUB.SUBDEL.ASM * * SUB.SUBINS.ASM * * SUB.SUBPOS.ASM * * SUB.SUBCHARCNT.ASM * * SUB.SUBTOK.ASM * * * * LIST OF MACROS * * * * SPOS : FIND SUBSTRING POS * * SCOP : SUBSTRING COPY * * SDEL : SUBSTRING DELETE * * SINS : SUBSTRING INSERT * * STOK : TOKENIZED SUBSTRING * * SCNT : CHARACTER COUNT * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ``` --- ### THE SPOS MACRO _SUMMARY_ | Condition | Value | | --------------- | -------------------------------------------------------- | | Name | `SPOS` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Find the position of a substring
in another string. | | Input | ]1 = Source string
]2 = Substring | | Output | none | | Dependencies | `SUB.SUBPOS.ASM` | | Flags Destroyed | NZCV | | Cycles | 205+ | | Bytes | 136 | | Notes | None | | See Also | `SUB.SUBPOS.ASM` | --- *DETAILS* The `SPOS`macro finds the position of a given substring within a larger string. The one byte index is stored in **RETURN** as well as in the **.A** register. `LISTING 5.31: The SCPOS Macro Source` ```assembly * *``````````````````````````````* * SPOS (NATHAN RIGGS) * * * * FIND THE POSITION OF A SUB- * * STRING IN A GIVEN STRING. * * * * PARAMETERS * * * * ]1 = SOURCE STRING * * ]2 = SUBSTRING * * * * CYCLES: 205+ * * SIZE: 136 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SPOS MAC _MSTR ]1;WPAR2 ; {14C13B} _MSTR ]2;WPAR1 ; {14C13B} JSR SUBPOS ; {177C110B} <<< * ``` --- ### THE SUBPOS SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ----------------------------------------------------- | | Name | `SUBPOS` | | Type | Subroutine | | File | `SUB.SUBPOS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | find index of substring | | Input | WPAR1 = Substring address
WPAR2 = String address | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 171+ | | Bytes | 107 | | Notes | none | | See Also | `SPOS` | --- *DETAILS* The `SUBPOS` macro finds the index position of a substring within a string, both of which are pointed to by the subroutine's arguments. The position is a single byte length, and is held in **RETURN** and in the **.A** register. If no substring is found, then a value of #255 ($FF) is returned. `LISTING 5.32: The SUBPOS Subroutine Source` ```assembly *``````````````````````````````* * SUBPOS (NATHAN RIGGS) * * * * RETURNS THE POSITION OF A * * SUBSTRING IN A GIVEN STRING. * * IF NO SUBSTRING IS FOUND, * * THEN #255 IS PASSED BACK * * INSTEAD. * * * * INPUT: * * * * WPAR1 = SUBSTRING (ADDRESS) * * WPAR2 = STRING ADDRESS * * * * DESTROYS: NZCIDV * * ^^^ ^ * * * * CYCLES: 171+ * * SIZE: 107 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STRIND EQU VARTAB ; STRING INDEX ]SUBIND EQU VARTAB+1 ; SUBSTRING INDEX ]CNT EQU VARTAB+2 ; COUNTER ]STRLEN EQU VARTAB+3 ; STRING LENGTH ]SUBLEN EQU VARTAB+4 ; SUBSTRING LENGTH ]SIDX EQU VARTAB+6 ; STRING INDEX ]SUB EQU WPAR1 ; ZP SUBSTRING ADDR PTR ]STR EQU WPAR2 ; ZP STRING ADDR POINTER * SUBPOS LDY #0 ; {3C2B} RESET INDEX COUNTER LDA (]STR),Y ; {5C2B} GET LENGTH OF STRING BEQ :NOTFND ; {3C2B} EXIT IF LENGTH = 0 STA ]STRLEN ; {4C3B} STORE STRING LENGTH LDA (]SUB),Y ; {5C2B} GET SUBSTR LENGTH BEQ :NOTFND ; {3C2B} EXIT IF SUB LENGTH = 0 STA ]SUBLEN ; {4C3B} STORE SUBSTRING LENGTH LDA ]SUBLEN ; {4C3B} IF SUBSTRING LENGTH IS CMP ]STRLEN ; {4C3B} > STRING LENGTH, DECLARE BEQ :LENOK ; {3C2B} THE STRING NOT FOUND BCS :NOTFND ; {3C2B} OTHERWISE, CONTINUE :LENOK LDA #1 ; {3C2B} SET STRING INDEX TO STA ]STRIND ; {4C3B} THE FIRST CHARACTER LDA ]STRLEN ; {4C3B} GET STRING LENGTH SEC ; {2C1B} SET CARRY SBC ]SUBLEN ; {4C3B} SUBTRACT SUBSTRING LENGTH STA ]CNT ; {4C3B} STORE AS COUNTER INC ]CNT ; {5C2B} INCREASE BY 1 :SLP1 LDA ]STRIND ; {4C3B} STA ]SIDX ; {4C3B} LDA #1 ; {3C2B} START SUBSTRING INDEX STA ]SUBIND ; {4C3B} AT 1 :CMPLP LDY ]SIDX ; {4C3B} LOAD STRING INDEX TO .7 LDA (]STR),Y ; {5C2B} GET NEXT CHAR FROM STR LDY ]SUBIND ; {4C3B} LOAD SUBSTRING INDEX TO .Y CMP (]SUB),Y ; {5C2B} COMPARE TO NEXT SUB CHAR BNE :SLP2 ; {3C2B} NOT A MATCH; BRANCH CPY ]SUBLEN ; {4C3B} TEST IF SUB INDEX = SUB LENGTH BEQ :FOUND ; {3C2B} IF SO, FOUND THE SUBSTRING INY ; {2C1B} ELSE INC TO NEXT CHAR STY ]SUBIND ; {4C3B} STORE NEW SUBSTRING INDEX INC ]SIDX ; {5C2B} INCREASE STRING INDEX JMP :CMPLP ; {3C3B} LOOP UNTIL DONE :SLP2 INC ]STRIND ; {5C2B} INCREMENT INDEX DEC ]CNT ; {5C2B} DEC COUNT BNE :SLP1 ; {3C2B} LOOP BACK IF UNFINISHED BEQ :NOTFND ; {3C2B} ELSE EXIT TO NOT FOUND :FOUND LDA ]STRIND ; {4C3B} FOUND, STORE INDEX IN .A JMP :EXIT ; {3C3B} :NOTFND LDA #255 ; {3C2B} SUB NOT FOUND, .A = 255 :EXIT STA RETURN ; {4C3B} STORE INDEX OR 255 IN RETURN LDY #1 ; {3C2B} STORE BYTE LENGTH OF 1 STY RETLEN ; {4C3B} INTO RETLEN LDY ]SUBLEN ; {4C3B} RTS ; {6C1B} ``` --- ### THE SCPY MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SCPY` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Copy a substring from another string | | Input | ]1 = Source string
]2 = Substring index
]3 = Substring length | | Output | none | | Dependencies | `SUB.SUBCOPY.ASM` | | Flags Destroyed | NZCV | | Cycles | 91+ | | Bytes | 71 | | Notes | None | | See Also | `SUB.SUBCOPY.ASM` | --- *DETAILS* The `SCPY` macro copies a substring from a larger string starting at the given index and continuing until the given length. The substring is passed back in **RETURN** with its length in **RETLEN**. Additionally, the length is passed back via the **.A** register. `LISTING 5.33: The SCPY Macro Source` ```assembly * *``````````````````````````````* * SCPY (NATHAN RIGGS) * * * * COPY SUBSTRING FROM STRING * * * * PARAMETERS * * * * ]1 = SOURCE STRING * * ]2 = SUBSTRING INDEX * * ]3 = SUBSTRING LENGTH * * * * CYCLES: 91+ * * SIZE: 71 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCPY MAC _MSTR ]1;WPAR1 ; {14C13B} LDA ]2 ; {4C3B} STA BPAR2 ; {3C2B} LDA ]3 ; {4C3B} STA BPAR1 ; {3C2B} JSR SUBCOPY ; {63C48B} <<< * ``` --- ### THE SUBCOPY SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SUBCOPY` | | Type | Subroutine | | File | `SUB.SUBCOPY.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | copy a substring | | Input | WPAR1 = Source string address
BPAR1 = Substring length
BPAR2 = Substring index | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 47+ | | Bytes | 25 | | Notes | none | | See Also | `SCPY` | --- *DETAILS* The `SUBCOPY` subroutine accepts a string address, and index and a length and then copies the part of the string that starts at the index through the length provided. The string itself is then stored in **RETURN** with its length in both **RETLEN** and the **.A** register. `LISTING 5.34: The SUBCOPY Subroutine Source` ```assembly *``````````````````````````````* * SUBCOPY (NATHAN RIGGS) * * * * INPUT: * * * * BPAR1 = SUBSTRING LENGTH * * BPAR2 = SUBSTRING INDEX * * WPAR1 = SOURCE STRING ADDR * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 47+ * * SIZE: 25 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]SUBLEN EQU BPAR1 ; SUBSTRING LENGTH ]SUBIND EQU BPAR2 ; SUBSTRING INDEX ]STR EQU WPAR1 ; SOURCE STRING * SUBCOPY LDY ]SUBIND ; {3C2B} STARTING COPY INDEX LDA ]SUBLEN ; {3C2B} SUBSTRING LENGTH STA RETLEN ; {4C3B} STORE SUB LENGTH IN RETLEN LDX #0 ; {3C2B} :COPY LDA (]STR),Y ; {5C2B} GET SUBSTRING CHARACTER STA RETURN,X ; {5C2B} STORE CHAR IN RETURN CPX ]SUBLEN ; {3C2B} IF .X COUNTER = SUBSTRING LENGTH BEQ :EXIT ; {3C2B} THEN FINISHED WITH LOOP INY ; {2C1B} OTHERWISE, INCREMENT .Y INX ; {2C1B} AND INCREMENT .X CLC ; {2C1B} CLEAR CARRY FOR FORCED BRANCH BCC :COPY ; {3C2B} LOOP :EXIT LDA ]SUBLEN ; {3C2B} RETURN SUBSTRING LENGTH IN .A RTS ; {6C1B} ``` --- ### THE SDEL MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SDEL` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Delete a substring from a string | | Input | ]1 = Source string
]2 = Substring index
]3 = Substring length | | Output | none | | Dependencies | `SUB.SUBDEL.ASM` | | Flags Destroyed | NZCV | | Cycles | 135+ | | Bytes | 79 | | Notes | None | | See Also | `SUB.SUBDEL.ASM` | --- *DETAILS* The `SUBDEL` macro deletes a substring from a larger string that starts at the given index and continues until the given length has been met. The new string (with no substring) is held in **RETURN**, with its length in both **RETLEN** and the **.A** register. `LISTING 5.35: The SUBDEL Macro Source` ```assembly * *``````````````````````````````* * SDEL (NATHAN RIGGS) * * * * DELETE SUBSTRING FROM STRING * * * * PARAMETERS * * * * ]1 = SOURCE STRING * * ]2 = SUBSTRING INDEX * * ]3 = SUBSTRING LENGTH * * * * CYCLES: 135+ * * SIZE: 79 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SDEL MAC _MSTR ]1;WPAR1 ; {14C13B} LDA ]2 ; {4C3B} STA BPAR2 ; {3C2B} LDA ]3 ; {4C3B} STA BPAR1 ; {3C2B} JSR SUBDEL ; {107C56B} <<< * ``` --- ### THE SUBDEL SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SUBDEL` | | Type | Subroutine | | File | `SUB.SUBDEL.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | delete a substring | | Input | WPAR1 = Source string address
BPAR1 = Substring length
BPAR2 = Substring index | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 101+ | | Bytes | 53 | | Notes | none | | See Also | `SDEL` | --- *DETAILS* The `SUBDEL` subroutine accepts a string address, an index and a length, then deletes a substring from the source string at the given index through to the given length. The string with the deleted substring is then held in **RETURN** with its length in **RETLEN** and the **.A** register. `LISTING 5.36: The SDEL Macro Source` ```assembly *``````````````````````````````* * SUBDEL (NATHAN RIGGS) * * * * DELETE A SUBSTRING FROM A * * LARGER STRING. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * BPAR2 = SUBSTRING INDEX * * BPAR1 = SUBSTRING LENGTH * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 101+ * * SIZE: 53 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]SUBLEN EQU BPAR1 ]SUBIND EQU BPAR2 ]STR EQU WPAR1 * SUBDEL DEC ]SUBIND ; {5C2B} INC ]SUBLEN ; {5C2B} LDY #0 ; {3C2B} RESET .Y INDEX LDA (]STR),Y ; {5C2B} GET STRING LENGTH SEC ; {2C1B} SET CARRY SBC ]SUBLEN ; {4C3B} SUBTRACT SUBSTRING LENGTH STA RETLEN ; {4C3B} STORE NEW LENGTH IN RETLEN INC RETLEN ; {5C2B} :LP1 INY ; {2C1B} INCREASE .Y INDEX LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING STA RETLEN,Y ; {5C2B} STORE IN RETURN CPY ]SUBIND ; {4C3B} IF .Y != SUBSTRING INDEX BNE :LP1 ; {3C2B} THEN CONTINUE LOOPING LDX ]SUBIND ; {4C3B} OTHERWISE, .X = SUB INDEX TYA ; {2C1B} TRANSFER .Y INDEX TO .A CLC ; {2C1B} CLEAR CARRY ADC ]SUBLEN ; {4C3B} ADD .Y TO SUBSTRING LENGTH TAY ; {2C1B} FOR NEW POS, THEN BACK TO .Y DEX ; {2C1B} DEY ; {2C1B} :LP2 INY ; {2C1B} INCREMENT .Y INDEX INX ; {2C1B} INCREMEMNT .X INDEX LDA (]STR),Y ; {5C2B} GET CHAR AFTER SUBSTRING STA RETURN,X ; {5C2B} STORE IN RETURN AT .X CPX RETLEN ; {4C3B} IF .X != NEW STRING LENGTH, BNE :LP2 ; {3C2B} CONTINUE LOOPING :EXIT LDA RETLEN ; {4C3B} LOAD NEW STRING LENGTH IN .A RTS ; {6C1B} *CPY #255 ; IF AT LENGTH MAX *BEQ :EXIT ; THEN QUIT COPYING ``` --- ### THE SINS MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SINS` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Insert a substring into another string | | Input | ]1 = Source string
]2 = Substring
]3 = Substring index | | Output | none | | Dependencies | `SUB.SUBINS.ASM` | | Flags Destroyed | NZCV | | Cycles | 177+ | | Bytes | 111 | | Notes | None | | See Also | `SUB.SUBINS.ASM` | --- *DETAILS* The `SUBINS` macro inserts a given substring at a specific index in another string. This new string is then held in **RETURN** with the length-byte stored in **RETLEN**. `LISTING 5.37: The SUBINS Macro Source` ```assembly * *``````````````````````````````* * SINS (NATHAN RIGGS) * * * * INSERT SUBSTRING INTO STRING * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * ]2 = SUBSTRING ADDRESS * * ]3 = SUBSTRING INDEX * * * * CYCLES: 177+ * * SIZE: 111 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SINS MAC _MSTR ]1;WPAR2 ; {14C13B} _MSTR ]2;WPAR1 ; {14C13B} LDA ]3 ; {4C3B} STA BPAR1 ; {3C2B} JSR SUBINS ; {142C82B} <<< * ``` --- ### THE SUBINS SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SUBINS` | | Type | Subroutine | | File | `SUB.SUBINS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | insert a substring | | Input | WPAR1 = Substring address
BPAR1 = String length
BPAR2 = Insertion index | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 136+ | | Bytes | 79 | | Notes | none | | See Also | `SINS` | --- *DETAILS* The `SUBINS` subroutine accepts a substring address, a source string address and an index, then inserts the substring into a copy of the source string at the specified index. This new string copy is then stored in **RETURN**, with the length stored in the **.A** register and **RETLEN**. `LISTING 5.38: The SUBINS Subroutine Source` ```assembly *``````````````````````````````* * SUBINS (NATHAN RIGGS) * * * * INSERT A SUBSTRING INTO A * * STRING AT A GIVEN POSITION. * * * * INPUT: * * * * WPAR1 = SUBSTRING ADDRESS * * WPAR2 = STRING ADDRESS * * BPAR1 = INSERTION INDEX * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 136+ * * SIZE: 79 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]SUB EQU WPAR1 ; SUBSTRING ADDRESS ]STR EQU WPAR2 ; STRING ADDRESS ]INDEX EQU BPAR1 ; STRING INDEX ]OLDIND EQU VARTAB ; OLD INDEX ]TMP EQU VARTAB+2 ; TEMPORARY VARIABLE ]SUBLEN EQU VARTAB+4 ; SUBSTRING LENGTH * SUBINS DEC ]INDEX ; {5C2B} LDY #0 ; {3C2B} SET .Y INDEX TO 0 LDA (]STR),Y ; {5C2B} GET STRING LENGTH STA ]TMP ; {4C3B} TEMPORARILY STORE LDA (]SUB),Y ; {5C2B} GET SUBSTRING LENGTH STA ]SUBLEN ; {4C3B} CLC ; {2C1B} CLEAR CARRY ADC ]TMP ; {4C3B} ADD SOURCE STRING LENGTH STA RETLEN ; {4C3B} STORE NEW STRING LENGTH BCC :CONT ; {3C2B} IF NO OVERFLOW, CONTINUE LDA #255 ; {3C2B} ELSE, NEW STRING LENGTH IS 255 STA RETLEN ; {4C3B} STORE IN RETLEN :CONT LDA ]INDEX ; {4C3B} IF INDEX IS 0, GO STRAIGHT BEQ :SUBCOPY ; {3C2B} TO COPYING SUBSTRING FIRST :LP1 INY ; {2C1B} INCREASE INDEX LDA (]STR),Y ; {5C2B} GET SOURCE STRING CHARACTER STA RETLEN,Y ; {4C3B} STORE IN RETURN CPY ]INDEX ; {3C2B} IF WE DON'T HIT SUBSTRING INDEX BNE :LP1 ; {3C2B} KEEP ON COPYING :SUBCOPY STY ]OLDIND ; {4C3B} STORE CURRENT STRING INDEX TYA ; {2C1B} TRANSFER .Y COUNTER TO TAX ; {2C1B} .X COUNTER TEMPORARILY LDY #0 ; {3C2B} RESET .Y COUNTER :SUBLP INY ; {2C1B} INCREASE .Y SUBSTRING INDEX INX ; {2C1B} CONTINUE INCREASING .X INDEX LDA (]SUB),Y ; {5C2B} LOAD INDEXED CHAR FROM SUBSTRING STA RETLEN,X ; {5C2B} STORE INTO RETURN AT INDEX CPY ]SUBLEN ; {4C3B} IF .Y != SUBSTRING LENGTH BNE :SUBLP ; {3C2B} THEN CONTINUE COPYING LDY ]OLDIND ; {4C3B} RESTORE OLD INDEX :FINLP INY ; {2C1B} INCREASE ORIGINAL INDEX INX ; {2C1B} INCREASE NEW INDEX LDA (]STR),Y ; {5C2B} LOAD NEXT CHAR FROM STRING STA RETLEN,X ; {5C2B} AND STORE AFTER SUBSTRING CPY ]TMP ; {4C3B} IF ORIGINAL STRING LENGTH BNE :FINLP ; {3C2B} IS NOT YET HIT, KEEP LOOPING :EXIT LDA RETLEN ; {4C3B} RETURN NEW LENGTH IN .A RTS ; {6C1B} ``` --- ### THE STOK MACRO _SUMMARY_ | Condition | Value | | --------------- | --------------------------------------------------- | | Name | `STOK` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Find a substring by token | | Input | ]1 = String
]2 = Token
]3 = Token Number | | Output | none | | Dependencies | `SUB.SUBTOK.ASM` | | Flags Destroyed | NZCV | | Cycles | 182+ | | Bytes | 118 | | Notes | None | | See Also | `SUB.SUBTOK.ASM` | --- *DETAILS* The `STOK` macro searches a string for a given token and passes through the specified number of tokens before copying a substring from that token to the next token. If the token number is #0, then the substring pulled will be whatever text comes become the first token. The substring is held in **RETURN**, with its length in **RETLEN**. Note that this macro has no protection against overflow, and thus extra care should be taken not to request a token number that extends beyond the number of tokens+1 (the extra token is due to having an implied token at the beginning of the string). The number of tokens available can be found by using the `SCNT` macro, which counts the number of occurrences of a character in a string. `LISTING 5.39: The STOK Macro Source` ```assembly * *``````````````````````````````* * STOK (NATHAN RIGGS) * * * * THIS MACRO FINDS AN ARGUMENT * * WITHIN A STRING THAT IS * * SEPARATED BY TOKENS AT A * * GIVEN INDEX. * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * ]2 = DELIMITER / TOKEN * * ]3 = ARGUMENT NUMBER * * * * CYCLES: 182+ * * SIZE: 118 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * STOK MAC _MLIT ]1;WPAR1 ; {14C13B} LDA ]2 ; {4C3B} STA WPAR2 ; {3C2B} LDA ]3 ; {4C3B} STA BPAR2 ; {3C2B} JSR SUBTOK ; {154C95B} <<< * ``` --- ### THE SUBTOK SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------------------------ | | Name | `SUBTOK` | | Type | Subroutine | | File | `SUB.SUBTOK.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | retrieve substring by token | | Input | WPAR1 = String address
WPAR2 = Token
BPAR2 = Token number | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 148+ | | Bytes | 92 | | Notes | none | | See Also | `STOK` | --- *DETAILS* The `SUBTOK` subroutine accepts a string address, a token character and a token number as parameters, then steps through the string as it counts the number of tokens. When the specified token number is found, the substring following it is copied to **RETURN** up until either another token is encountered or the end of the string is reached. The length of this substring is held in both **RETLEN** and the **.A** register. Importantly, this subroutine does not protect against overflow. As such, the `SCNT` macro should be used to determine the number of tokens in a string if that number is not known beforehand. `LISTING 5.40: The SUBTOK Subroutine Source` ```assembly *``````````````````````````````* * SUBTOK (NATHAN RIGGS) * * * * THIS SUBROUTINE PULLS FROM A * * STRING OF ARGUMENTS THAT ARE * * SEPARATED BY A DELIMITER AND * * RETURNS THE ARGUMENT NUMBER * * THAT IS REQUESTED. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * WPAR2 = DELIMITER * * BPAR2 = ARGUMENT INDEX * * * * DESTROY: NZCIDV * * ^^^ ^ * * * * CYCLES: 148+ * * SIZE: 92 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]DELIM EQU WPAR2 ; DELIMITER ]LEN EQU BPAR1 ; STRING LENGTH ]ARG EQU BPAR2 ; ARGUMENT NUMBER ]START EQU VARTAB ; START POS OF ARGUMENT ]END EQU VARTAB+2 ; END POS OF ARGUMENT ]COUNT EQU VARTAB+4 ; ARGUMENT COUNTER ]XFLAG EQU VARTAB+6 ; LOOP EXIT FLAG ]FOUND EQU VARTAB+8 ; ARGUMENT FOUND FLAG * SUBTOK LDY #0 ; {3C2B} CLEAR .Y REGISTER STY ]COUNT ; {4C3B} CLEAR COUNTER STY ]XFLAG ; {4C3B} CLEAR LOOP FLAG LDA (]STR),Y ; {5C2B} LOAD STRING LENGTH STA ]LEN ; {3C2B} AND STORE IN LENGTH VARIABLE STY ]START ; {4C3B} CLEAR START POS STY ]END ; {4C3B} CLEAR ENDING POS STY ]FOUND ; {4C3B} CLEAR FOUND FLAG TAX ; {2C1B} CLEAR .X REGISTER :_LP2 INY ; {2C1B} INCREASE INDEX LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING CMP ]DELIM ; {3C2B} COMPARE TO DELIMITER BEQ :CHECK ; {3C2B} IF EQUAL, GOTO :CHECK :RELOOP CPY ]LEN ; {3C2B} ELSE, COMPARE .Y TO LENGTH BNE :_LP2 ; {3C2B} IF NOT EQUAL, LOOP AGAIN LDX #1 ; {3C2B} LOAD #1 IN .X STX ]XFLAG ; {4C3B} SET LOOP EXIT FLAG :CHECK STY ]END ; {4C3B} STORE CUR INDEX TO END POS LDX ]ARG ; {3C2B} LOAD ARGUMENT NUMBER CPX ]COUNT ; {4C3B} COMPARE TO CURRENT COUNT BEQ :COPY ; {3C2B} IF EQUAL, PROCEED TO :COPY INC ]COUNT ; {5C2B} OTHERWISE, INCREASE COUNT STY ]START ; {4C3B} STORE CURRENT INDEX AS START JMP :RELOOP ; {3C3B} GOTO BEGINNING LOOP :COPY LDY ]START ; {4C3B} LOAD STARTING POSITION IN .Y LDX #255 ; {3C2B} LOAD -1 IN .X REGISTER LDA ]XFLAG ; {4C3B} LOAD LOOP EXIT FLAG CMP #1 ; {3C2B} AND COMPARE TO #1 BEQ :CPLP ; {3C2B} IF SET, GOTO COPY LOOP DEC ]END ; {5C2B} OTHERWISE, DECREASE END POS :CPLP INY ; {2C1B} INCREASE .Y INDEX INX ; {2C1B} INCREASE .X INDEX LDA (]STR),Y ; {5C2B} LOAD CHARACTER FROM STRING STA RETURN,X ; {5C2B} STORE IN RETURN CPY ]END ; {4C3B} IF .Y INDEX != END POS BNE :CPLP ; {3C2B} THEN CONTINUE COPY LOOP :SETLEN LDA ]END ; {4C3B} LOAD ENDING POS SEC ; {2C1B} SET CARRY SBC ]START ; {4C3B} SUBTRACT STARTING POS STA RETLEN ; {4C3B} AND STORE IN RETLEN :EXIT RTS ; {6C1B} ``` --- ### THE SCNT MACRO _SUMMARY_ | Condition | Value | | --------------- | ------------------------------------------ | | Name | `SCNT` | | Type | Macro | | File | `MAC.SUBSTRINGS.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | Count occurrence of characters in a string | | Input | ]1 = String
]2 = Character | | Output | none | | Dependencies | `SUB.SUBCHARCNT.ASM` | | Flags Destroyed | NZCV | | Cycles | 88+ | | Bytes | 57 | | Notes | None | | See Also | `SUB.SUBCHARCNT.ASM` | --- *DETAILS* The `SCNT` macro counts the number of times a character appears in a string. This is especially useful for tokenized strings that need to be parsed into other variables, such as the classic comma-delimited string used prevalently for arrays and spreadsheets. This macro is doubly important due to the fact that the `STOK` macro and the `SUBTOK` subroutine do not check for value overflows, meaning that the `SCNT` macro must be relied on for strings with an unknown number of tokens. `LISTING 5.41: The SCNT Macro Source` ``` * *``````````````````````````````* * SCNT (NATHAN RIGGS) * * * * * * PARAMETERS * * * * ]1 = STRING ADDRESS * * ]2 = DELIMITER / TOKEN * * * * CYCLES: 88+ * * SIZE: 57 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * SCNT MAC _MSTR ]1;WPAR1 ; {14C13B} LDA ]2 ; {4C3B} STA WPAR2 ; {3C2B} JSR SUBCHARCNT ; {67C39B} <<< ``` --- ### THE SUBCHARCNT SUBROUTINE _SUMMARY_ | Condition | Value | | --------------- | ----------------------------------------------------- | | Name | `SUBCHARCNT` | | Type | Subroutine | | File | `SUB.SUBCHARCNT.ASM` | | Author | Nathan Riggs | | Last Revision | 12-APR-2021 | | Assembler | Merlin Pro 8 | | OS | Apple DOS 3.3 | | Purpose | count character occurrences in string | | Input | WPAR1 = String address
WPAR2 = Character to find | | Output | none | | Dependencies | none | | Flags Destroyed | NZCV | | Cycles | 61+ | | Bytes | 36 | | Notes | none | | See Also | `SCNT` | --- *DETAILS* The `SUBCHARCNT` subroutine counts the number of occurrences of a character within a string, storing that number (byte) in both **RETLEN** and the **.A** register. While potentially useful on its own, this subroutine is immediately available to work in tandem with the `STOK` macro (or the `SUBTOK` subroutine), since it does not protect against overflow. `SUBCHARCNT` can be used to count the number of tokens in a string before `STOK` is used to retrieve a substring by token number. `LISTING 5.41: The SUBCHARCNT Subroutine Source` ```assembly *``````````````````````````````* * SUBCHARCNT (NATHAN RIGGS) * * * * COUNT THE NUMBER OF TOKENS, * * OR OCCURRENCES OF A GIVEN * * CHARACTER, IN A STRING. * * * * INPUT: * * * * WPAR1 = STRING ADDRESS * * WPAR2 = CHARACTER TO FIND * * * * DESTROYS: NZCIDV * * ^^^ ^ * * * * CYCLES: 61+ * * SIZE: 36 BYTES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ]STR EQU WPAR1 ; STRING ADDRESS ]CHAR EQU WPAR2 ; TOKEN TO FIND ]LEN EQU BPAR2 ; LENGTH OF STRING * SUBCHARCNT LDA #0 ; {3C2B} CLEAR OUT .A REGISTER TAY ; {2C1B} CLEAR OUT .Y TAX ; {2C1B} CLEAR OUT .X LDA (]STR),Y ; {5C2B} GET LENGTH FROM STRING STA ]LEN ; {3C2B} AND HOLD TEMPORARILY :LP1 INY ; {2C1B} INCREASE .Y INDEX LDA (]STR),Y ; {5C2B} LOAD CHAR AT INDEX CMP ]CHAR ; {3C2B} COMPARE TO TOKEN BEQ :FOUND ; {3C2B} IF EQUAL, THEN GOTO :FOUND :RELOOP CPY ]LEN ; {3C2B} COMPARE .Y TO STRING LENGTH BNE :LP1 ; {3C2B} RELOOP UNTIL EQUAL JMP :EXIT ; {3C3B} JUMP OVER REST :FOUND INX ; {2C1B} INCREASE .X COUNTER JMP :RELOOP ; {3C3B} JUMP BACK TO LOOP :EXIT STX RETURN ; {4C3B} STORE .X COUNT IN RETURN LDA #1 ; {3C2B} LOAD #1 AND STA RETLEN ; {4C3B} STORE IN RETLEN TXA ; {2C1B} ALSO RETURN NUMBER IN .A RTS ; {6C1B} ``` --- # PART II: STRING AND SUBSTRING DEMONSTRATIONS The following demo files illustrate how each macro in the collection is used. This includes two demos, one for whole strings and one for substrings. `LISTING 5.50: Whole Strings Demonstration Source` ```assembly * *``````````````````````````````* * DEMO.STRINGS.ASM * * * * A DEMO OF THE VARIOUS MACROS * * FOR STRING HANDLING. * * * * AUTHOR: NATHAN RIGGS * * CONTACT: NATHAN.RIGGS@ * * OUTLOOK.COM * * * * DATE: 12-APR-2021 * * ASSEMBLER: MERLIN 8 PRO * * OS: DOS 3.3 * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** ASSEMBLER DIRECTIVES * CYC AVE EXP OFF TR ON DSK DEMO.STRINGS OBJ $BFE0 ORG $6000 * *``````````````````````````````* * TOP INCLUDES (PUTS, MACROS) * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * PUT MIN.HEAD.REQUIRED.ASM USE MIN.MAC.REQUIRED.ASM USE MIN.MAC.STRINGS.ASM PUT MIN.HEAD.STRINGS.ASM ]HOME EQU $FC58 * *``````````````````````````````* * PROGRAM MAIN BODY * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * *``````````````````````````````* * STRING MACROS * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THIS FILE CONTAINS MACRO DEMONSTRATIONS FOR THE ** STRING COLLECTION OF THE APPLEIIASM LIBRARY. NOTE ** THAT ANOTHER DEMO FILE EXISTS THAT IS DEDICATED TO ** MACROS THAT SPECIFICALLY DEAL WITH SUBSTRINGS, ** WHERAS THIS FILE DEMONSTRATES MACROS THAT WORK ON ** THE WHOLE STRING. * *``````````````````````````````* * STRING REVERSE * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SREV MACRO SIMPLY ACCEPTS A STRING ** OR ITS ADDRESS AND REVERSES IT, HANDING ** BACK THE REVERSED STRING IN RETURN WITH ** ITS LENGTH IN RETLEN. * JSR ]HOME _PRN "STRING REVERSAL",8D _PRN "===============",8D8D LDA #"-" JSR $FDF0 SREV #S2 _AXLIT #RETLEN JSR _PS LDA #"-" JSR $FDF0 _WAIT * *``````````````````````````````* * STRING TRIMMING * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE STRIM MACRO ACCEPTS A GIVEN TOKEN OR ** DELIMITER TO CHECK FOR AT THE BEGINNING OR ** END OF A STRING, THEN DELETES THEM FROM THE ** STRING IF THEY EXIST AT EITHER END. * JSR ]HOME _PRN "STRING TRIMMING",8D _PRN "===============",8D8D STRIM #S2;#" " _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING UPPERCASE * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE STRUP MACRO ACCEPTS A STRING AND ** RETURNS ITS UPPERCASE EQUIVALENT. * JSR ]HOME _PRN "STRING UPPERCASING",8D _PRN "==================",8D8D STRUP #S6 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING LOWERCASE * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SLO MACRO TAKES A STRING AND MAKES ** EVERY CHARACTER LOWERCASE, PASSING THE NEW ** STRING BACK IN RETURN WITH ITS LENGTH IN ** RETLEN. NOTE THAT *ALL* CHARACTERS ARE TURNED ** TO LOWERCASE THAT ARE ALREADY UPPERCASE, SO EVEN ** WORDS THAT SHOULD BE CAPITALIZED WILL BE ** IN LOWERCASE. YOU CAN REMEDY THIS, IF NEEDED, WITH ** THE SCAP MACRO. * JSR ]HOME _PRN "STRING LOWERCASING",8D _PRN "==================",8D8D STRLO #S1 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING CAPITALIZATION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SCAP MACRO ACCEPTS A STRING AND SCANS ** IT FOR WORDS THAT SHOULD BE CAPITALIZED, ** INCLUDING THE FIRST LETTER OF THE STRING. ** WHILE NOT ALL NEEDS ARE MET WITH THIS, AS ** IT ONLY CAPITALIZES BASED ON WHETHER A NEW ** SENTENCE IS STARTED, IT SHOULD SERVE MOST ** PURPOSES. * JSR ]HOME _PRN "STRING CAPITALIZATION",8D _PRN "=====================",8D8D SCAP #S5 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING CONCATENATION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SCAT MACRO CONCATENATES (OR JOINS) ** ONE STRING TO ANOTHER AND PASSES THE NEW ** STRING BACK IN RETURN, WITH THE NEW LENGTH ** HELD IN RETLEN. * JSR ]HOME _PRN "STRING CONCATENATION",8D _PRN "====================",8D8D SCAT #S3;#S4 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING COMPARISON * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SCMP MACRO COMPARES ONE STRING TO ANOTHER, ** RETURNING THE COMPARISON RESULTS VIA THE STATUS ** REGISTER AS FOLLOWS: * ** Z = 1 IF STRINGS MATCH ** Z = 0 IF STRINGS DON'T MATCH ** C = 0 IF 1ST STRING < 2ND STRING LENGTH ** C = 1 IF IST STRING >= 2ND STRING LENGTH * ** NOTE THAT THIS MIRRORS, IN WAYS, HOW THE CMP ** INSTRUCTION WORKS. * JSR ]HOME _PRN "STRING COMPARISON",8D _PRN "=================",8D8D SCMP #S1;#S2 BCC :ALTB BCS :AGTEB :ALTB _PRN "STRING 1 IS LESS THAN STRING 2.",8D8D JMP :_END :AGTEB _PRN "STRING 1 IS GREATER THAN OR EQUAL TO",8D _PRN "STRING 2.",8D8D :_END _WAIT * JSR ]HOME _PRN "FIN!",8D8D * * * JMP $3D0 * ** THE FOLLOWING SUBROUTINE IS A COPY OF THE STDIO ** LIBRARY'S PRNSTR SUBROUTINE, WHICH IS USED TO ** PRINT STRINGS WITH A PRECEDING LENGTH-BYTE. THIS ** IS USED FOR SHOWING THE OUTPUT OF THE VARIOUS ** STRING MACROS. * ]STRLEN HEX 0000 ]COUT1 EQU $FDF0 _PS STA ADDR1 STX ADDR1+1 LDY #0 LDA (ADDR1),Y STA ]STRLEN :_LP INY LDA (ADDR1),Y JSR ]COUT1 CPY ]STRLEN BNE :_LP LDA ]STRLEN RTS * S1 STR "ONE RING TO RULE THEM ALL" S2 STR " ONE RING TO FIND THEM " S3 STR " ONE RING TO BRING THEM ALL" S4 STR "AND IN THE DARKNESS BIND THEM " S5 STR "this is a test? a test. a test! a test." S6 STR "this is a test." S7 STR "ZERO ONE TWO THREE FOUR FIVE SIX" * *``````````````````````````````* * BOTTOM INCLUDES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** BOTTOM INCLUDES * PUT MIN.LIB.REQUIRED.ASM * ** INDIVIDUAL SUBROUTINE INCLUDES * ** STRING SUBROUTINES * PUT MIN.SUB.STRCAT.ASM PUT MIN.SUB.STRCOMP.ASM PUT MIN.SUB.STRTRIM.ASM PUT MIN.SUB.STRUPPER.ASM PUT MIN.SUB.STRCAP.ASM PUT MIN.SUB.STRLOWER.ASM PUT MIN.SUB.STRREV.ASM ``` `LISTING 5.51: Substrings Demonstration Source` ```assembly * *``````````````````````````````* * DEMO.SUBSTRINGS.ASM * * * * A DEMO OF THE VARIOUS MACROS * * FOR HANDLING SUBSTRINGS. * * * * AUTHOR: NATHAN RIGGS * * CONTACT: NATHAN.RIGGS@ * * OUTLOOK.COM * * * * DATE: 11-APR-2021 * * ASSEMBLER: MERLIN 8 PRO * * OS: DOS 3.3 * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** ASSEMBLER DIRECTIVES * CYC AVE EXP OFF TR ON DSK DEMO.SUBSTRINGS OBJ $BFE0 ORG $6000 * *``````````````````````````````* * TOP INCLUDES (PUTS, MACROS) * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * PUT MIN.HEAD.REQUIRED.ASM USE MIN.MAC.REQUIRED.ASM USE MAC.SUBSTRINGS.ASM PUT MIN.HEAD.STRINGS.ASM ]HOME EQU $FC58 * *``````````````````````````````* * PROGRAM MAIN BODY * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THIS FILE CONTAINS DEMONSTRATIONS OF THE MACROS ** DEDICATED TO SUBSTRINGS IN THE STRINGS COLLECTION ** OF THE APPLEIIASM LIBRARY. NOTE THAT ANOTHER ** DEMO FILE EXISTS FOR OPERATIONS ON FULL STRINGS. * *``````````````````````````````* * TOKENIZED SUBSTRING RETRIEVE * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE STOK MACRO RETRIEVES A SUBSTRING FROM A ** LARGER STRING THAT IS FOUND BY A PRECEDING ** TOKEN. THE ENTIRE STRING CONSISTS OF SUBSTRINGS ** SEPARATED BY TOKENS (A SPACE, A COMMA, ETC.), AND ** THE SUBSTRING IS RETRIEVED BY SPECIFYING THE TOKEN ** NUMBER PRECEDING IT. * ** NOTE THAT THERE IS CURRENTLY NO SAFETY MEASURE TO ** PREVENT OVERFLOW HERE. TO BE SPECIFIC, IT IS ** POSSIBLE TO ATTEMPT TO RETRIEVE A TOKEN SUBSTRING ** THAT DOES NOT EXIST, SUCH AS A SEVENTH SUBSTRING ** WHERE ONLY SIX EXIST. TO GUARD AGAINST THIS, THE ** SCNT MACRO SHOULD BE USED TO ASCERTAIN THE NUMBER OF ** TOKENS IN THE STRING (SEE BELOW). * ** ADDITIONALLY, IT IS IMPORTANT TO KNOW THAT TOKEN ** NUMBER ZERO IS COUNTED, MEANING THE TEXT BEFORE ** THE FIRST OCCURENCE OF A TOKEN. IF A STRING HAS ** SIX TOKENS IN IT, THEN, IT WILL TECHNICALLY HAVE ** SEVEN SUBSTRINGS. * JSR ]HOME _PRN "TOKENIZED SUBSTRINGS",8D _PRN "====================",8D8D STOK #S7;#" ";#6 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * STRING TOKEN COUNTING * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SCNT MACRO COUNTS THE NUMBER OF TOKENS, ** OR THE NUMBER OF TIMES A GIVEN CHARACTER IS ** PRESENTED IN A STRING. THIS IS MOSTLY USEFUL ** IN CONJUNCTION WITH THE STOK MACRO, AS IT ** PROVIDES THE NUMBER OF SUBSTRINGS AVAILABLE ** TO BE RETRIEVED. * ** NOTE THAT WHEN USED WITH THE STOK MACRO, ** ONE SHOULD BE ADDED TO THE RESULT OF SCNT ** TO FIND THE TOTAL NUMBER OF SUBSTRINGS THAT ** ARE AVAILABLE. THIS IS DUE TO THE FACT THAT ** THE STOK MACRO STARTS WITH A ZERO INDEX, ** COUNTING THE TEXT BEFORE THE FIRST TOKEN AS ** A TOKENIZED SUBSTRING AS WELL. * JSR ]HOME _PRN "STRING TOKEN COUNTING",8D _PRN "=====================",8D8D SCNT #S7;#" " DUMP #RETURN;#1 _WAIT * *``````````````````````````````* * SUBSTRING POSITION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SPOS MACRO FINDS THE POSITION OF A SUBSTRING ** IN A GIVEN STRING AND RETURNS IT. NOTE THAT THIS ** MACRO ONLY RETURNS THE POSITION OF THE FIRST ** INSTANCE OF THE SUBSTRING, AND DOES NOT ACCOUNT ** FOR REPETITIONS. * ** NOTE THAT THIS MACRO USES A STARTING INDEX OF ONE ** FOR A MORE INTUITIVE FEEL. WHEN USED IN CONJUNCTION ** WITH ZERO-INDEXED MACROS, CARE SHOULD BE TAKEN TO ** ADD OR SUBTRACT TO THIS VALUE ACCORDINGLY. * JSR ]HOME _PRN "SUBSTRING POSITION",8D _PRN "==================",8D8D SPOS #S1;"RING" DUMP #RETURN;#1 _WAIT * *``````````````````````````````* * SUBSTRING INSERTION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SINS MACRO INSERTS A GIVEN SUBSTRING INTO ** ANOTHER GIVEN STRING AT THE PROVIDED INDEX. THE ** NEW STRING IS PASSED BACK VIA RETURN, WITH ITS ** LENGTH BYTE IN RETLEN. * JSR ]HOME _PRN "SUBSTRING INSERTION",8D _PRN "===================",8D8D SINS #S1;#S7;#10 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * SUBSTRING COPY * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SCPY MACRO COPIES A SUBSTRING FROM A GIVEN ** STRING AT AN INDEX AND LENGTH. THE RESULTING ** SUBSTRING IS STORED IN RETURN, WITH ITS LENGTH ** HELD IN RETLEN. * JSR ]HOME _PRN "SUBSTRING COPY",8D _PRN "==============",8D8D SCPY #S1;#5;#10 _AXLIT #RETLEN JSR _PS _WAIT * *``````````````````````````````* * SUBSTRING DELETION * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** THE SDEL MACRO TAKES A STRING, AN INDEX AND A ** SUBSTRING LENGTH AND DELETES THAT SUBSTRING ** FROM THE LARGER STRING, PASSING BACK THE RESULTING ** STRING IN RETURN WITH ITS LENGTH IN RETLEN. * JSR ]HOME _PRN "SUBSTRING DELETION",8D _PRN "==================",8D8D SDEL #S1;#5;#10 _AXLIT #RETLEN JSR _PS _WAIT * JSR ]HOME _PRN " ",8D8D _PRN "FIN!",8D8D * JMP $3D0 * ** THE FOLLOWING SUBROUTINE IS A COPY OF THE STDIO ** LIBRARY'S PRNSTR SUBROUTINE, WHICH IS USED TO ** PRINT STRINGS WITH A PRECEDING LENGTH-BYTE. THIS ** IS USED FOR SHOWING THE OUTPUT OF THE VARIOUS ** STRING MACROS. * ]STRLEN HEX 0000 ]COUT1 EQU $FDF0 _PS STA ADDR1 STX ADDR1+1 LDY #0 LDA (ADDR1),Y STA ]STRLEN :_LP INY LDA (ADDR1),Y JSR ]COUT1 CPY ]STRLEN BNE :_LP LDA ]STRLEN RTS * S1 STR "ONE RING TO RULE THEM ALL" S2 STR " ONE RING TO FIND THEM " S3 STR " ONE RING TO BRING THEM ALL" S4 STR "AND IN THE DARKNESS BIND THEM " S5 STR "this is a test? a test. a test! a test." S6 STR "this is a test." S7 STR "ZERO ONE TWO THREE FOUR FIVE SIX" * *``````````````````````````````* * BOTTOM INCLUDES * *,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,* * ** BOTTOM INCLUDES * PUT MIN.LIB.REQUIRED.ASM * ** INDIVIDUAL SUBROUTINE INCLUDES * ** STRING SUBROUTINES * PUT SUB.SUBTOK.ASM PUT SUB.SUBCOPY.ASM PUT SUB.SUBDEL.ASM PUT SUB.SUBINS.ASM PUT SUB.SUBPOS.ASM PUT SUB.SUBCHARCNT.ASM ```