In terms of a simple system ....String handling functions are all about counting and comparing and indexes, so if I was writing functions in basic I'd probably use an approach like this:
String Definition Table
Have a RAM or EEPROM lookup table containing string definitions. Call it 'RAM_StringDefinitionTable'.
Assuming all strings are stored in the same place (you could make this more complex by allowing different places), then the definition for a string would simply be it's start and end address. For instance, suppose all strings are stored in the Scratchpad of a 28X1. So a string is defined by the start and end address in the scratchpad.
The StringDetailsTable holds all the string definitions as a series of 2-byte Definition records (1st byte is the start address, second byte end address of the string).
Lets say you refer to strings by their 'StringID', which is really just their record number.
Defining strings
You need to write a little routine ...lets call it 'String_Define' to define a string. So if you have a block of bytes that you want to define as a string, you call this routine and give the start and end address, and also the StringID. The routine then loads the start and end into the relevant record of the table.
String Functions
The routines you write all need to refer to this StringDefinitionTable. For instance, LEFT (let's call it 'String_Left') ...
You need to pass the String_Left routine the StringID, and the ByteCount. The routine returns a new string, which is ByteCount leftmost bytes of the string. By 'return a new string' I mean a String definition record is defined in the StringDefinitionTable. It might be an idea to reserve a record in the table purely for the result of any String Function. For instance StringID 10, could always be used to hold the result.
String_Mid would be similar, and you pass the routine the StringID, StartByte and ByteCount. And then return the result string details to StringID = 10. In fact the String_Left and String_Right routines are just special cases of String_Mid, so really you just need to focus on the code for String_Mid.
You could even have functions to search for one string in another string, and find/replace etc etc !
I don't think the code for these routines would be too long.
PS: Don't get thinking I'm using any standard names when I talk about StringDefinitionTable ....you just make up names and a system that makes sense to you ...that's why it's all good creative fun
EDIT: here's some code based on this approach. Untested. Note this approach doesn't actually move any bytes around at all. If you want to do that you'd need to modify it as required.
Code:
'DEFINITIONS:
'============
'String Variables:
Symbol StartAddress = b0
Symbol EndAddress = b1
Symbol StringID = b3
Symbol ByteCount = b4
Symbol Index = b5
'General variables:
Symbol Address = b6
'Constants
Symbol RAM_StringDefinitionTable = 80
Symbol ResultID = 10
'SUBROUTINES:
'============
String_Define:
'Populates a record in the String Definition Table
'ON ENTRY: String_StartAddress = the start address
' String_EndAddress = the end address
' StringID = the ID of the string to define. 0 to 9.
'ON EXIT: The record in the String Defrinition Table relating to StringID is defined.
Address = StringID * 2 + RAM_StringDefinitionTable
Poke Address,StartAddress,EndAddress
Return
String_MID:
'Returns a string containing ByteCount middle bytes of the specified string.
'ON ENTRY: StringID = the ID of the source string.
' Index = the position within the string to start from.
' ByteCount = the number of bytes to extract.
'ON EXIT: The ResultID string holds the middle bytes.
Address = StringID * 2 + RAM_StringDefinitionTable
Peek Address,StartAddress
StartAddress = Index + StartAddress
EndAddress = StartAddress + ByteCount - 1
Gosub String_Define
Return
String_LEFT:
'Returns a string containing ByteCount leftmost bytes of the specified string.
'ON ENTRY: StringID = the ID of the source string.
' ByteCount = the number of bytes to extract.
'ON EXIT: The ResultID string holds the leftmost bytes.
Index = 0
Gosub String_MID
Return
String_RIGHT:
'Returns a string containing ByteCount Rightmost bytes of the specified string.
'ON ENTRY: StringID = the ID of the source string.
' ByteCount = the number of bytes to extract.
'ON EXIT: The ResultID string holds the rightmost bytes.
Address = StringID * 2 + RAM_StringDefinitionTable + 1
Peek Address,EndAddress
StartAddress = EndAddress - ByteCount + 1
Gosub String_Define
Return