It is often necessary to split a string into pieces
based on several different (potentially multi-character) separator strings,
while still retaining the information about which separators were present in the input.

This is particularly useful when doing small parsing tasks. 

The task is to write code to demonstrate this.

The function (or procedure or method, as appropriate) should
take an input string and an ordered collection of separators.

The order of the separators is significant: 

The delimiter order represents priority in matching, with the first defined delimiter having the highest priority.
In cases where there would be an ambiguity as to
which separator to use at a particular point
(e.g., because one separator is a prefix of another)
the separator with the highest priority should be used.
Delimiters can be reused and the output from the function should be an ordered sequence of substrings.

Test your code using the input string “a!===b=!=c” and the separators “==”, “!=” and “=”.

For these inputs the string should be parsed as "a" (!=) "" (==) "b" (=) "" (!=) "c", where matched delimiters are shown in parentheses, and separated strings are quoted, so our resulting output is "a", empty string, "b", empty string, "c".
Note that the quotation marks are shown for clarity and do not form part of the output.

Extra Credit: provide information that indicates which separator was matched at each separation point and where in the input string that separator was matched.

# split a string based on a number of separators #
 
# MODE to hold the split results #
MODE SPLITINFO = STRUCT( STRING text      # delimited string, may be empty          #
                       , INT    position  # starting position of the token          #
                       , STRING delimiter # the delimiter that terminated the token #
                       );
# calculates the length of string s #
OP   LENGTH = ( STRING s )INT: ( UPB s + 1 ) - LWB s;
# returns TRUE if s starts with p, FALSE otherwise #
PRIO STARTSWITH = 5;
OP   STARTSWITH = ( STRING s, p )BOOL: IF LENGTH p > LENGTH s THEN FALSE ELSE s[ LWB s : ( LWB s + LENGTH p ) - 1 ] = p FI;
# returns an array of SPLITINFO describing the tokens in str based on the delimiters #
# zero-length delimiters are ignored #
PRIO SPLIT = 5;
OP   SPLIT = ( STRING str, []STRING delimiters )[]SPLITINFO:
     BEGIN
        # count the number of tokens #
        # allow there to be as many tokens as characters in the string + 2 #
        # that would cater for a string composed of delimiters only        #
        [ 1 : ( UPB str + 3 ) - LWB str ]SPLITINFO tokens;
        INT   token count   := 0;
        INT   str pos       := LWB str;
        INT   str max        = UPB str;
        BOOL  token pending := FALSE;
        # construct the tokens #
        str pos       := LWB str;
        INT prev pos  := LWB str;
        token count   := 0;
        token pending := FALSE;
        WHILE str pos <= str max
        DO
            BOOL found delimiter := FALSE;
            FOR d FROM LWB delimiters TO UPB delimiters WHILE NOT found delimiter DO
                IF LENGTH delimiters[ d ] > 0 THEN
                    IF found delimiter := str[ str pos : ] STARTSWITH delimiters[ d ] THEN
                        token count          +:= 1;
                        tokens[ token count ] := ( str[ prev pos : str pos - 1 ], prev pos, delimiters[ d ] );
                        str pos              +:= LENGTH delimiters[ d ];
                        prev pos              := str pos;
                        token pending         := FALSE
                    FI
                FI
            OD;
            IF NOT found delimiter THEN
                # the current character is part of s token #
                token pending := TRUE;
                str pos      +:= 1
            FI
        OD;
        IF token pending THEN
            # there is an additional token after the final delimiter #
            token count +:= 1;
            tokens[ token count ] := ( str[ prev pos : ], prev pos, "" )
        FI;
        # return an array of the actual tokens #
        tokens[ 1 : token count ]
     END # SPLIT # ;
 
 
# test the SPLIT operator #
[]SPLITINFO test tokens = "a!===b=!=c" SPLIT []STRING( "==", "!=", "=" );
FOR t FROM LWB test tokens TO UPB test tokens DO
    SPLITINFO token = test tokens[ t ];
    print( ( "token: [",  text OF token, "] at: ", whole( position OF token, 0 ), " delimiter: (", delimiter OF token, ")", newline ) )
OD