SMOLNET PORTAL home about changes
(**DynamicStrings.Mod provides an implementation of dynamic strings
using a linked list of records holding ARRAY OF CHAR.  *)
MODULE DynamicStrings;
  IMPORT Strings;

  CONST vSize = 128;
  TYPE
    DynamicString* = POINTER TO DynamicStringDesc;
    DynamicStringDesc* = RECORD 
      value : ARRAY vSize OF CHAR; 
      next : DynamicString 
    END;

  (** New* will initialize a new dynamic string settings `.value` to
  an empty string and `.next` to NIL. *)
  PROCEDURE New*(VAR str : DynamicString);
  BEGIN NEW(str);
    str.value := ""; 
    str.next := NIL;
  END New;


  (** Set* copies an `ARRAY OF CHAR` into a DynamicString. This requires 
   that we add and link additional records if the `source` is longer than 
   `vSize`. *)
  PROCEDURE Set*(VAR str : DynamicString; source : ARRAY OF CHAR); 
    VAR cur, next : DynamicString; tmp : ARRAY vSize OF CHAR; 
        i, l : INTEGER;
  BEGIN cur := str; cur.value := "";
    l := Strings.Length(source);
    i := 0; 
    WHILE i < l DO
      Strings.Extract(source, i, i + vSize, tmp);
      Strings.Append(tmp, cur.value);
      i := i + Strings.Length(tmp);
      IF (i < l) THEN
        IF cur.next = NIL THEN
          New(next); cur.next := next;
        END;
        cur := cur.next;
      END; 
    END;
  END Set;

  
  (** GetChar* returns the character in the dynamic string at the
      position indicated. The value of c will hold the 
      character found or 0X, the value ok will hold TRUE if
      character was found or FALSE if it wasn't found. *)
  PROCEDURE GetChar*(s : DynamicString; pos : INTEGER; VAR c : ARRAY OF CHAR);
    VAR i, j : INTEGER; cur : DynamicString; continue : BOOLEAN;
  BEGIN
    cur := s;
    i := 0;
    c := "";
    continue := TRUE;
    WHILE (cur # NIL) & (i <= pos) & continue DO
      FOR j := 0 TO Strings.Length(cur.value) DO
        IF i = pos THEN
          Strings.Extract(cur.value, i, 1, c);
          continue := FALSE;
        END;
        INC(i);
      END;
      cur := cur.next;
    END;
  END GetChar;

  (** SetChar* sets a char in the dynamic string as the position given.
      If the set was successful ok will be set TRUE or FALSE otherwise. *)
  PROCEDURE SetChar*(VAR s : DynamicString; pos : INTEGER; c : ARRAY OF CHAR);
    VAR i, j : INTEGER; cur : DynamicString; continue : BOOLEAN;
      val : ARRAY 2 OF CHAR;
  BEGIN
    cur := s;
    i := 0;
    (* Make sure we are only setting one character *)
    Strings.Extract(c, 0, 1, val);
    continue := TRUE;
    WHILE (cur # NIL) & (i <= pos) & continue DO
      FOR j := 0 TO Strings.Length(cur.value) DO
        IF i = pos THEN
          Strings.Replace(val, j, cur.value);
          continue := FALSE;
        END;
        INC(i);
      END;
      cur := cur.next;
    END;
  END SetChar;


  (** ToCharArray* will copy a dynamic string into a suitable sized 
      ARRAY OF CHAR. The value of `ok` is set TRUE on success, FALSE
      if truncatation is required. *)
  PROCEDURE ToCharArray*(str : DynamicString; 
                         VAR dest : ARRAY OF CHAR; 
                         VAR ok : BOOLEAN);
    VAR cur : DynamicString; i : INTEGER;
  BEGIN 
    ok := FALSE;
    cur := str; i := 0;
    WHILE cur # NIL DO
      i := i + Strings.Length(cur.value);
      Strings.Append(cur.value, dest);
      cur := cur.next;
    END;
    ok := (i = Strings.Length(dest));
  END ToCharArray;


  (** Append* appends a dynamic string, extra, to the destination
      dynamic string called dest.  Memory is allocated during
      copying of the appended string's records.
      *)
  PROCEDURE Append*(extra: DynamicString; VAR dest : DynamicString);
    VAR cur : DynamicString;  
  BEGIN
    (* Move to the end of the dest DynamicString *)
    cur := dest;
    WHILE cur.next # NIL DO cur := cur.next; END;
    (* Starting initial pointer of `extra` copy its records
       input new records created in `cur`. *)
    WHILE extra # NIL DO
      (* Create a new record *)
      NEW(cur.next);
      cur.next.value := "";
      cur.next.next := NIL;
      (* Copy extra.value into new record *)
      Strings.Extract(extra.value, 0, vSize, cur.next.value);
      (* Advance to next record for both cur and extra *)
      extra := extra.next;
      cur := cur.next;
    END;
  END Append;

  (** AppendCharArray* copies an ARRAY OF CHAR into a new dynamic
      string, then uses `Append` to append the new dynamic string
      to our dest dynamic string. *)
  PROCEDURE AppendCharArray*(extra: ARRAY OF CHAR; VAR dest : DynamicString);
    VAR extraStr : DynamicString;    
  BEGIN
    (* Convert our extra ARRAY OF CHAR into a DynamicString *)
    New(extraStr); Set(extraStr, extra);
    (* Now we can append. *)
    Append(extraStr, dest);
  END AppendCharArray;

  (** Length() returns an INTEGER indicating the total length for
      all the records making up the dynamic string. *)
  PROCEDURE Length*( source : DynamicString) : INTEGER;
    VAR cur : DynamicString; total : INTEGER;
  BEGIN
    total := 0;
    cur := source;
    WHILE cur # NIL DO
      total := total + Strings.Length(cur.value);
      cur := cur.next;
    END; 
    RETURN total
  END Length;

  (** Insert inserts a "extra" dynamic string into a destination dynamic
      string at a given position. *)
  PROCEDURE Insert*(extra : DynamicString; 
                    pos : INTEGER; 
                    VAR dest : DynamicString);
    VAR cur, rest : DynamicString;
        tmp : ARRAY vSize OF CHAR;
        i, splitPos : INTEGER; continue : BOOLEAN;
  BEGIN
    (* 1. Set `cur` to the start of our `dest` dynamic string *)
    cur := dest;

    (* 2. Move to the record which holds the split point *)
    i := 0;
    continue := (i < pos);
    WHILE continue DO
      i := i + Strings.Length(cur.value);
      continue := (i < pos);
      IF continue & (cur.next # NIL) THEN
        cur := cur.next;
      ELSE
        continue := FALSE;
      END;
    END;

    (* 3. Copy the characters in `cur.value` from relative split point to end of `.value` into a temporary `ARRAY OF CHAR` *)
    splitPos := pos MOD vSize;
    Strings.Extract(cur.value, splitPos, Strings.Length(cur.value), tmp);

    (* 4. Make a new record, `rest`, using the temporary `ARRAY OF CHAR` and update the value of `.next` to match that of `cur.next` *)
    New(rest); Set(rest, tmp);
    rest.next := cur.next;

    (* 5. Truncate `cur.value` at the relative split point *)
    i := splitPos;
    WHILE i < LEN(cur.value) DO
      cur.value[i] := 0X;
      INC(i);
    END;

    (* 6. Set `cur.next` to point to our `extra` dynamic string. *)
    cur.next := extra;

    (* 7. Move to the end of extra with `cur` *)
    WHILE cur.next # NIL DO cur := cur.next; END;

    (* 8. Set the `cur.next` to point at `rest` *)
    cur.next := rest;
  END Insert;

  (** InsertCharArray creates a new dynamic string from source
      and inserts it using the original `Insert` procedure for
      dynamic strings. *)
  PROCEDURE InsertCharArray*(source : ARRAY OF CHAR; 
                             pos : INTEGER; 
                             VAR dest : DynamicString);
    VAR extra : DynamicString;
  BEGIN
    New(extra); Set(extra, source);
    Insert(extra, pos, dest);
  END InsertCharArray;

  (** Delete replaces the characters in the dynamic string
      from pos until n unlinking any un-used records. The GC 
      should then beable to recover the memory assuming no 
      additional pointers. *)
  PROCEDURE Delete*(VAR source : DynamicString; pos, n : INTEGER);
  BEGIN
  END Delete;

  (** Replace replaces src in the destination at the given
      position.  It is the equivalent of dones in a delete
      followed by insert. *)  
  PROCEDURE Replace*(source: DynamicString; 
                     pos : INTEGER; 
                     VAR dest : DynamicString);
  BEGIN
    (* This procedure has be left for the reader to implement. *)
  END Replace;

  (** Extract *)
  PROCEDURE Extract*(source : DynamicString;
                     pos, n : INTEGER; 
                     VAR dest : DynamicString);
  BEGIN
    (* This procedure has be left for the reader to implement. *)
  END Extract;

  (** Pos *)
  PROCEDURE Pos*(pattern : DynamicString; pos : INTEGER) : INTEGER;
    VAR p : INTEGER;
  BEGIN
    (* This procedure has be left for the reader to implement. *)
    p := 0;
    RETURN p
  END Pos;

  (** Cap *)
  PROCEDURE Cap*(VAR s : DynamicString);
  BEGIN
    (* This procedure has be left for the reader to implement. *)
  END Cap;

END DynamicStrings.
Response: text/plain
Original URLgopher://sdf.org/0/users/rsdoiel/blog/2020/05/25/DynamicS...
Content-Typetext/plain; charset=utf-8