IMPLEMENTATION MODULE String;

FROM Streams IMPORT StdOut;
FROM Conversions IMPORT IntConversions;
IMPORT SYSTEM;

INLINE
  %#include <string.h>
  END;

  ONCE CLASS String;
  
  METHOD BreakInWords (Source, 
                       SkipSeparator, 
                       KeepSeparator : ARRAY OF CHAR): ARRAY OF ARRAY OF CHAR;
  VAR
    i, WordPos: INTEGER;                
    StartPos, Len: ARRAY OF INTEGER;
    KeepArr, SkipArr: ONCE ARRAY OF BOOLEAN;
        
    METHOD NewWord;
      BEGIN       
      IF Len [WordPos] > 0 THEN
        WordPos := WordPos + 1;
        END;  
      END NewWord;
    
  BEGIN
  IF Source <> VOID THEN       
    IF KeepArr = VOID THEN
      KeepArr.CREATE (256);
      SkipArr.CREATE (256);
      END;
    FOR k := 0 TO 255 DO
      KeepArr[k] := FALSE;
      END;
    FOR k := 0 TO 255 DO
      SkipArr[k] := FALSE;
      END;
    IF SkipSeparator <> VOID THEN
      FOR k := 0 TO SkipSeparator.SIZE - 1 DO
        SkipArr[SYSTEM.ORD(SkipSeparator[k])] := TRUE;
        END;
      END;
    IF KeepSeparator <> VOID THEN
      FOR k := 0 TO KeepSeparator.SIZE - 1 DO
        KeepArr[SYSTEM.ORD(KeepSeparator[k])] := TRUE;
        END;
      END;
        StartPos.CREATE (Source.SIZE+1);
        Len.CREATE (Source.SIZE+1); 
        WHILE i < Source.SIZE DO
          IF SkipArr[SYSTEM.ORD(Source[i])] THEN
            NewWord;
            StartPos [WordPos] := i+1;
           ELSIF KeepArr[SYSTEM.ORD(Source[i])] THEN 
            NewWord;
            Len [WordPos] := 1;
            StartPos[WordPos] := i;
            NewWord;
            StartPos [WordPos] := i+1;
           ELSIF Source[i] = '"' THEN  -- Quote handling !
            NewWord;
            IF i < Source.SIZE - 1 THEN
              i := i + 1;
              StartPos [WordPos] := i;
              WHILE (i < Source.SIZE) AND (Source [i] <> '"') DO
                i := i + 1;
                Len[WordPos] := Len[WordPos] + 1;
                END;
              WordPos := WordPos + 1;
              StartPos [WordPos] := i+1;
              NewWord;
              END;
           ELSE
            Len[WordPos] := Len[WordPos] + 1;
            END;  
          i := i + 1;
          END; 
        NewWord;
        IF WordPos > 0 THEN                 
        IF Len[WordPos] = 0 THEN
          WordPos := WordPos - 1;
          END;
        RESULT.CREATE (WordPos+1);
        FOR j := 0 TO WordPos DO
          RESULT [j] := Source.SLICE (StartPos[j], Len[j]);
          END;
      END;
    END;
  END BreakInWords;  
  

    METHOD Strip(a: ARRAY OF CHAR): ARRAY OF CHAR;
      VAR
        First, Last: INTEGER;
      BEGIN
      IF (a <> VOID) AND (a.SIZE > 0) THEN
        WHILE (First < a.SIZE) AND (a[First] = ' ') DO
          First := First + 1;
          END;
        IF First < a.SIZE THEN
          Last := a.SIZE;
          WHILE (Last > 0) AND (a[Last-1] = ' ') DO
            Last := Last - 1;
            END;
          RESULT := a.SLICE(First,Last-First);
         ELSE
          RESULT := "";
          END;
       ELSE
        RESULT := "";
        END;
      END Strip;

    METHOD LTrim (a : ARRAY OF CHAR) : ARRAY OF CHAR;
      VAR
        i : INTEGER;
      BEGIN
      ASSERT (a <> VOID);
      RESULT := a;
      IF a.SIZE > 0 THEN
        IF a[0] <> " " THEN
          -- Ok, no leading blanks, let's return
         ELSE
          -- Let's find the first non blank char
          i := 0;
          WHILE (i < a.SIZE) AND (a [i] = " ") DO
            i := i + 1;
            END;
          RESULT := a.SLICE (i, a.SIZE - i);  
          END;
        END;
      END LTrim;
      
    METHOD RTrim (a : ARRAY OF CHAR) : ARRAY OF CHAR;
      VAR
        i : INTEGER;
      BEGIN
      ASSERT (a <> VOID);
      RESULT := a;
      IF a.SIZE > 0 THEN
        IF a[a.SIZE-1] <> " " THEN
          -- Ok, no trailing blanks, let's return
         ELSE
          -- Let's find the first non blank char
          i := a.SIZE-1;
          WHILE (i >= 0) AND (a [i] = " ") DO
            i := i - 1;
            END;
          RESULT := a.SLICE (0, i+1);  
          END;
        END;
      END RTrim;
      
    METHOD Right(a: ARRAY OF CHAR;
                 Size: INTEGER): ARRAY OF CHAR;
      VAR
        TheSize: INTEGER;
      BEGIN
      ASSERT a <> VOID;
      IF Size >= a.SIZE THEN
        TheSize := a.SIZE;
       ELSE
        TheSize := Size;
        END;
      RESULT := a.SLICE (a.SIZE - TheSize, TheSize);
      END Right;

    METHOD Left(a: ARRAY OF CHAR;
                Size: INTEGER): ARRAY OF CHAR;
      VAR
        TheSize: INTEGER;
      BEGIN
      ASSERT a <> VOID;
      IF Size >= a.SIZE THEN
        TheSize := a.SIZE;
       ELSE
        TheSize := Size;
        END;
      RESULT := a.SLICE (0, TheSize);
      END Left;

    METHOD Copy(From,
                To: ARRAY OF CHAR;
                FromPos, ToPos, Len: INTEGER);
       BEGIN
       ASSERT From <> VOID;
       ASSERT To <> VOID;
       ASSERT FromPos + Len <= From.SIZE;
       ASSERT ToPos + Len <= To.SIZE;
       FOR i := 0 TO Len-1 DO
         To [ToPos + i] := From [FromPos + i];
         END;
       END Copy;

    METHOD AddChar (a: ARRAY OF CHAR; Ch: CHAR): ARRAY OF CHAR;
      BEGIN
      RESULT.CREATE(a.SIZE + 1);
      Copy(a, RESULT, 0, 0, a.SIZE);
      RESULT[a.SIZE]:= Ch;
      END AddChar;

    METHOD Insert (Source: ARRAY OF CHAR;
                   Pos: INTEGER;
                   What: ARRAY OF CHAR): ARRAY OF CHAR;
      BEGIN
      ASSERT Source <> VOID;
      IF What = VOID THEN
        RESULT := Source.CLONE;
       ELSE
        ASSERT Pos >= 0;
        ASSERT Pos <= Source.SIZE;
        IF Pos = Source.SIZE THEN
          RESULT := Source+What;
         ELSE
          ASSERT Pos < Source.SIZE;
          RESULT.CREATE (Source.SIZE + What.SIZE);
          Copy (Source, RESULT, 0, 0, Pos);
          Copy (What, RESULT, 0, Pos, What.SIZE);
          Copy (Source, RESULT, Pos, Pos + What.SIZE, Source.SIZE - Pos);
          END;
        END;
      END Insert;

    METHOD Delete (Source: ARRAY OF CHAR; Pos, Len: INTEGER): ARRAY OF CHAR;
      VAR
        TheLen: INTEGER;
      BEGIN
      ASSERT Source <> VOID;
      IF Len > 0 THEN
        ASSERT Pos >= 0;
        ASSERT Pos < Source.SIZE;
        TheLen := Len;
        IF Pos + TheLen > Source.SIZE THEN
          TheLen := Source.SIZE - Pos;
          END;
        RESULT.CREATE (Source.SIZE - TheLen);
        Copy (Source, RESULT, 0, 0, Pos);
        Copy (Source, RESULT, Pos + TheLen, Pos, Source.SIZE - Pos - TheLen);
       ELSE
        RESULT := Source.CLONE;
        END;
      END Delete;

    METHOD Pos (Source: ARRAY OF CHAR; Ch: CHAR): INTEGER;
      VAR
         Len: INTEGER;
      BEGIN
--      ASSERT Source <> VOID;
--      Len := Source.SIZE;
--      INLINE
--        % {
--        %   ychar *p = (ychar*) Y_Source;
--        %   int Y_RESULT=0;
--        %
--        %   while ((Y_RESULT < Y_Len) && (*p != (ychar) Y_Ch))
--        %     {
--        %     Y_RESULT ++;
--        %     p++;
--        %     }
--        % }
--        END;
      Len := Source.SIZE;
      RESULT := Len;
      FOR i := 0 TO Source.SIZE - 1 WHILE RESULT = Len DO
        IF Source[i] = Ch THEN
          RESULT := i;
          END;
        END;
      ASSERT (RESULT = Source.SIZE) OR (Source[RESULT] = Ch);
      END Pos;

    METHOD PosString (Source, Pattern: ARRAY OF CHAR): INTEGER;
      VAR
        Ok: BOOLEAN;
      BEGIN
      RESULT := -1;
      Ok := TRUE;
      FOR i := 0 TO Source.SIZE - Pattern.SIZE WHILE RESULT < 0 DO
        Ok := TRUE;
        FOR j := 0 TO Pattern.SIZE - 1 WHILE Ok DO
          Ok := Source[i+j] = Pattern[j];
          END;
        IF Ok THEN
          RESULT := i;
          END;
        END;
      IF RESULT = -1 THEN
        RESULT := Source.SIZE;
       ELSE      
        ASSERT Equals (Pattern, Source.SLICE (RESULT, Pattern.SIZE));
        END;
      END PosString;

    METHOD LimitedCompare(One, Two: ARRAY OF CHAR;
                          Len: INTEGER): INTEGER;
      VAR
        i: INTEGER;
      BEGIN
      ASSERT One <> VOID;
      ASSERT Two <> VOID;
      ASSERT (Len <= One.SIZE) OR (Len <= Two.SIZE);
      INLINE
        % Y_i = strncmp(Y_One, Y_Two, (int) Y_Len);
        END;
      IF i = 0 THEN
        RESULT := Equal;
       ELSIF i < 0 THEN
        RESULT := Smaller;
       ELSE
        RESULT := Greater;
        END;
--      DEBUG
--        StdOut.WriteString (One + "/" + Two + "/");
--        StdOut.WriteInt (RESULT, 0);
--        StdOut.WriteLn;
--        END;
      END LimitedCompare;

    METHOD LimitedCompareNoCase(One, Two: ARRAY OF CHAR;
                                Len: INTEGER): INTEGER;
      VAR
        c1, c2: CHAR;
      BEGIN
      RESULT := Equal;
      IF One <> Two THEN
        FOR i := 0 TO Len - 1 WHILE (RESULT = Equal) DO
          c1 := SYSTEM.UCASE(One[i]);
          c2 := SYSTEM.UCASE(Two[i]);
          IF c1 > c2 THEN
            RESULT := Greater;
           ELSIF c1 < c2 THEN
            RESULT := Smaller;
            END;
          END;
        END;
      END LimitedCompareNoCase;
      
    METHOD IsPrefix (Short, Long: ARRAY OF CHAR): BOOLEAN;
      BEGIN
      RESULT := (Short.SIZE <= Long.SIZE) AND
        	    FOR_ALL i IN 0 TO Short.SIZE - 1 :- Long[i] = Short[i];
      END IsPrefix;
                      
    METHOD Compare(One, Two: ARRAY OF CHAR): INTEGER;
      VAR
        MinLen: INTEGER;
      BEGIN
      IF One.SIZE < Two.SIZE THEN
        MinLen := One.SIZE;
       ELSE
        MinLen := Two.SIZE;
        END;
      RESULT := LimitedCompare(One, Two, MinLen);
      IF RESULT = Equal THEN
        IF One.SIZE < Two.SIZE THEN
          RESULT := Smaller;
         ELSIF One.SIZE > Two.SIZE THEN
          RESULT := Greater;
          END;
        END;
      END Compare;

    METHOD CompareNoCase(One, Two: ARRAY OF CHAR): INTEGER;
      VAR
        MinLen: INTEGER;
      BEGIN
      IF One.SIZE < Two.SIZE THEN
        MinLen := One.SIZE;
       ELSE
        MinLen := Two.SIZE;
        END;
      RESULT := LimitedCompareNoCase(One, Two, MinLen);
      IF RESULT = Equal THEN
        IF One.SIZE < Two.SIZE THEN
          RESULT := Smaller;
         ELSIF One.SIZE > Two.SIZE THEN
          RESULT := Greater;
          END;
        END;
      END CompareNoCase;

    METHOD Equals (One, Two: ARRAY OF CHAR): BOOLEAN;
      BEGIN
      IF One.SIZE = Two.SIZE THEN
        RESULT := TRUE;
        FOR i := 0 TO One.SIZE - 1 WHILE RESULT DO
	  RESULT := One[i] = Two[i];
          END;
        END;
      END Equals;

    METHOD UpperCase (a: ARRAY OF CHAR): ARRAY OF CHAR;
      VAR
        Modified: BOOLEAN;
        Ch, Ch2: CHAR;
      BEGIN
      ASSERT (a <> VOID);
      RESULT := a;
      FOR i := 0 TO RESULT.SIZE-1 DO
        Ch := RESULT[i];
        Ch2 := SYSTEM.UCASE (Ch);
        IF Ch <> Ch2 THEN
          IF NOT Modified THEN
            RESULT := a.CLONE;
            Modified := TRUE;
            END;
          RESULT[i] := Ch2;
          END;
        END;
      END UpperCase;

    METHOD LowerCase (a: ARRAY OF CHAR): ARRAY OF CHAR;
      VAR
        Modified: BOOLEAN;
        Ch, Ch2: CHAR;
      BEGIN
      ASSERT (a <> VOID);
      RESULT := a;
      FOR i := 0 TO RESULT.SIZE-1 DO
        Ch := RESULT[i];
        Ch2 := SYSTEM.LCASE (Ch);
        IF Ch <> Ch2 THEN
          IF NOT Modified THEN
            RESULT := a.CLONE;
            Modified := TRUE;
            END;
          RESULT[i] := Ch2;
          END;
        END;
      END LowerCase;

    METHOD ParseEscapeSequence(Source: ARRAY OF CHAR): ARRAY OF CHAR;
      VAR
        Buffer: ONCE ARRAY OF CHAR;
        Len, i, j: INTEGER;
      CONST
        Esc = '\';
        Escape = 27;
      BEGIN
      IF (Buffer = VOID) OR (Buffer.SIZE < Source.SIZE) THEN
        Buffer.CREATE (Source.SIZE);
        END;
      WHILE i < Source.SIZE DO
        IF (Source[i] = Esc) AND (i+1 < Source.SIZE) THEN
          i := i + 1;
          CASE Source[i] OF
            ' ':
              Buffer[Len] := ' ';
              END;
            '[':
              Buffer[Len] := SYSTEM.CHR(Escape);
              END;
            't', 'T':
              Buffer[Len] := SYSTEM.CHR(SYSTEM.Tab);
              END;
            'n', 'N':
              Buffer[Len] := SYSTEM.CHR(SYSTEM.NewLine);
              END;
            '0' TO '9':
              j := IntConversions.StringToInt (Source.SLICE(i, 3));
              Buffer[Len] := SYSTEM.CHR(j);
              i := i + 2;
              END;
           ELSE
            Buffer [Len] := Source [i];
            END;
         ELSE
          Buffer [Len] := Source [i];
          END;
        i := i + 1;
        Len := Len + 1;
        END;
      IF Len = 0 THEN
        RESULT := VOID;
       ELSE
        RESULT := Buffer.SLICE (0, Len);
        END;
      END ParseEscapeSequence;

  END String;

END String;
