I would need to know your code better to understand how you are using the colon character. If you have a minute maybe i can tell you what i am doing to avoid exhausting the uses i can give to a word or character.
I made my engine in a way that any character can be used anywhere and have a different purpose depending on the statement it is working on, its pretty efficient and can parse and convert the whole code in less than 5 seconds (PowerBASIC takes 3.9 so, 1 second more is pretty acceptable).
My tokenizer first analyzes the code and labels all parts of it. For optimized speed it doesnt actually move data around, it only creates a "dictionary" of words, containing data about what kind of word it is and where in the memory each one is located. This is a pretty fast process, it usually takes like 0.4 seconds even for huge source codes. This process labels every word depending on what it is, %KW_RESERVED, %KW_CRLF... %KW_OPERATOR... %KW_EQUATE... etc. I have currently 177 types of different words.
After this point, checking strings directly is almost unnecessary, making it pretty fast. It only checks the word type byte (for %KW_XXX values) and an INTEGER value if the byte contains a %KW_RESERVED (for %RW_XXXX values).
After that, the engine analizes the dictionary and "expands" macros, in a second "expanded" dictionary where all the macros have been expanded and are ready to be evaluated. No words are analyzed during that process, and no strings are moved around, only the dictionary is updated, this makes it pretty fast too.
Then it goes word by word, and when it finds a %KW_CRLF (or a %KW_COLON or %KW_GBRACKETCLOSE if the previous statement ended with one of those), It then analizes what kind of word is next.
Suppose after a %KW_CRLF the engine finds a %KW_RESERVED, it then checks what reserved word it is and acts accordingy.
For example if it finds a %RW_FUNCTION in the dictionary, it invokes:
CALL handleModules(DICTIONARY(), Index)
This analizes the words, and if it is being used to define a function (by finding a %KW_BRACKETOPEN after the name) or if it is being used to return the value of a function (by finding %KW_EQUAL after %KW_RESERVED).
If it finds a %KW_FREEVAR after the reserved word, it then checks if there is a %KW_BRACKETOPEN, and if so, the bracket count goes up 1 and keeps looking for more code.
If at one point during this, it finds a %KW_COLON when the bracket count is 1, it "could" (it doesnt right now) be taken as a "special" separator, for name and value.
Then, if a later point the bracket count is decreased with %KW_BRACKETCLOSE, look if there is an %KW_RESERVED with %RW_EXPORT... then See if there is an %KW_RESERVED with %RW_AS... and then look for a %KW_DATATYPE. After that. It finally expects the end of the statement, it expects either a %KW_GBRACKETOPEN or %KW_COLON or %KW_CRLF.
During all this, the colon was used for two different purposes depending on what part of the statement it was analizing, so, in theory it would be impossible to exhaust the uses of a colon. It could have hundreds or thousands of uses and it is pretty fast!
Using this technique, i can use many words for many purposes without exhausting it's uses, for example, i can use the BIT word as follows:
STDOUT BIT(var, 1)
BIT SET var, 1
udtmem AS BIT * 4 IN BYTE
The CPU footprint is small, memory usage is low, and the uses of words are virtually unlimited.
By the way, if the tokenizer finds a %KW_COLON it checks if the previous found word was also a %KW_COLON, and if so, it converts the previous word in a %KW_NAMESPACESEPARATOR and discards the last %KW_COLON. This makes it easier to work with keywords at a later stage of the compilation.
This process (along with some clever indexing for huge lists of equates) can beat PowerBASIC's speed under some circumstances and get pretty close to it the rest of the times... all this without using assembly exclusively, so, i now i can optimize it even further by using more assembly in some areas.