suplib/src/PathComp.m3


 Copyright 1997-2003 John D. Polstra.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *      This product includes software developed by John D. Polstra.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 

MODULE PathComp;

IMPORT Pathname, SupMisc, Text;

REVEAL
  Compressor = CPublic BRANDED OBJECT
    root: Pathname.T;
    prev: Pathname.T;
    target: Pathname.T;
    file: TEXT;
    rootLen: CARDINAL;
    targLen: CARDINAL;
    curLen: CARDINAL;
    goal: CARDINAL;  (* Pathname length we are currently moving toward. *)
    rootIsAbsolute: BOOLEAN;
  OVERRIDES
    init := CInit;
    put := CPut;
    finish := CFinish;
    get := CGet;
  END;

PROCEDURE CInit(self: Compressor; root: Pathname.T := ""): Compressor =
  BEGIN
    self.root := root;
    self.prev := root;
    self.target := NIL;
    self.file := NIL;
    self.rootLen := Text.Length(self.root);
    self.curLen := self.rootLen;
    self.rootIsAbsolute := Pathname.Absolute(root);
    RETURN self;
  END CInit;

PROCEDURE CPut(self: Compressor; type: Type; path: Pathname.T)
  RAISES {Error} =
  VAR
    slashPos: INTEGER;
  BEGIN
    <* ASSERT self.target = NIL *>
    IF Pathname.Absolute(path) # self.rootIsAbsolute THEN
      RAISE Error("Absoluteness of path does not match root");
    END;
    CASE type OF
    | Type.DirDown =>
	self.target := path;
	self.file := NIL;
    | Type.File, Type.DirUp =>
	slashPos := Text.FindCharR(path, SupMisc.SlashChar);
	IF type = Type.File THEN
	  self.file := Text.Sub(path, slashPos+1);
	ELSE
	  self.file := NIL;
	END;
	IF slashPos <= 0 THEN  (* Special case for "" or "/". *)
	  INC(slashPos);
	END;
	self.target := Text.Sub(path, 0, slashPos);
    END;
    self.targLen := Text.Length(self.target);
    self.goal := SupMisc.CommonPathLength(self.prev, self.target);
    IF self.goal < self.rootLen THEN
      RAISE Error("Attempt to ascend above the root");
    END;
    IF self.curLen = self.goal THEN  (* No need to go up. *)
      self.goal := self.targLen;
    END;
  END CPut;

PROCEDURE CFinish(self: Compressor) =
  BEGIN
    self.target := self.root;
    self.targLen := self.rootLen;
    self.goal := self.rootLen;
    self.file := NIL;
  END CFinish;

PROCEDURE CGet(self: Compressor;
               VAR type: Type;
	       VAR name: TEXT): BOOLEAN =
  VAR
    slashPos, start, limit: INTEGER;
  BEGIN
    IF self.curLen > self.goal THEN  (* Going up. *)
      type := Type.DirUp;
      slashPos := Text.FindCharR(self.prev, SupMisc.SlashChar, self.curLen-1);
      name := Text.Sub(self.prev, slashPos+1, self.curLen - (slashPos+1));
      IF slashPos <= 0 THEN  (* Special case for "" or "/". *)
	self.curLen := slashPos + 1;
      ELSE
	self.curLen := slashPos;
      END;
      IF self.curLen <= self.goal THEN  (* Done going up. *)
	<* ASSERT self.curLen = self.goal *>
	self.goal := self.targLen;
      END;
      RETURN TRUE;
    ELSIF self.curLen < self.goal THEN  (* Going down. *)
      type := Type.DirDown;
      IF self.curLen = 0
      OR self.curLen = 1 AND self.rootIsAbsolute THEN
	(* Special case for "" or "/". *)
	start := self.curLen;
      ELSE
	start := self.curLen + 1;
      END;
      limit := Text.FindChar(self.target, SupMisc.SlashChar, start);
      IF limit = -1 THEN limit := self.goal END;
      name := Text.Sub(self.target, start, limit - start);
      self.curLen := limit;
      RETURN TRUE;
    ELSIF self.file # NIL THEN  (* In the right directory, emit filename. *)
      type := Type.File;
      name := self.file;
      self.file := NIL;
      RETURN TRUE;
    ELSE  (* Done. *)
      IF self.target # NIL THEN
	self.prev := self.target;
	self.target := NIL;
      END;
      RETURN FALSE;
    END;
  END CGet;
***************************************************************************

REVEAL
  Decompressor = DPublic BRANDED OBJECT
    current: Pathname.T;
  OVERRIDES
    init := DInit;
    put := DPut;
    getDir := DGetDir;
  END;

PROCEDURE DInit(self: Decompressor; root: Pathname.T := ""): Decompressor =
  BEGIN
    self.current := root;
    RETURN self;
  END DInit;

PROCEDURE DPut(self: Decompressor; type: Type; name: TEXT): Pathname.T =
  VAR
    path: Pathname.T;
  BEGIN
    CASE type OF
    | Type.DirDown =>
	self.current := SupMisc.CatPath(self.current, name);
	RETURN self.current;
    | Type.File =>
	RETURN SupMisc.CatPath(self.current, name);
    | Type.DirUp =>
	path := self.current;
	self.current := SupMisc.PathPrefix(self.current);
	RETURN path;
    END;
  END DPut;

PROCEDURE DGetDir(self: Decompressor): Pathname.T =
  BEGIN
    RETURN self.current;
  END DGetDir;

BEGIN
END PathComp.