-- CSImpl.mesa
-- last edit by Schmidt, May 6, 1983 1:01 pm
-- last edit by Satterthwaite, August 11, 1983 8:24 am
-- replacement for SubrImpl in the Cedar world

DIRECTORY
  CS: TYPE USING [FileErrorType, read, readWrite, write],
  DCSFileTypes: TYPE USING [tLeaderPage],
  Directory: TYPE USING [CreateFile, Error, ignore, Lookup, UpdateDates],
  Environment: TYPE USING [wordsPerPage],
  File: TYPE USING [Capability, Permissions, SetSize],
  FileStream: TYPE USING [Create],
  Inline: TYPE USING [DIVMOD, LongDivMod],
  IO: TYPE USING [
    card, GetChar, PFCodeProc, Put, PutF, PutFR, SetPFCodeProc,
    Signal, STREAM, string, Value],
  Rope: TYPE USING [Equal, Fetch, FromProc, Length, Lower, ROPE, Substr, Text],
  SafeStorage: TYPE USING [NewZone],
  Stream: TYPE USING [Handle],
  TimeStamp: TYPE USING [Stamp];
	
CSImpl: CEDAR PROGRAM 
    IMPORTS Directory, File, FileStream, Inline, IO, Rope, SafeStorage
    EXPORTS CS ~ {

 -- variables
  z: PUBLIC ZONE ← NIL;

 -- signals
  FileError: PUBLIC ERROR[error: CS.FileErrorType] ~ CODE;

 -- startup
  Init: PUBLIC PROC ~ {};	-- call to make sure module is started

 -- rope utilities

  RopeToString: PUBLIC UNSAFE PROC[to: LONG STRING, from: Rope.ROPE] ~ UNCHECKED {
    len: CARDINAL ~ from.Length;
    IF to.maxlength <= len THEN ERROR;
    FOR i: CARDINAL IN [0 .. len) DO
      to[i] ← from.Fetch[i];
      ENDLOOP;
    to.length ← len};

  RootName: PUBLIC PROC[name: Rope.ROPE] RETURNS[Rope.ROPE] ~ {
    i: CARDINAL ← name.Length - 1;
    WHILE i > 0 AND name.Fetch[i] ~= '. DO i ← i-1 ENDLOOP;
    RETURN[IF i > 0 THEN name.Substr[0, i] ELSE name]};

  EndsIn: PUBLIC PROC[str: Rope.ROPE, suf: Rope.Text] RETURNS[BOOL] ~ {
    strLength: CARDINAL ~ str.Length;
    sufLength: CARDINAL ~ suf.Length;
    RETURN[sufLength <= strLength AND
      suf.Equal[str.Substr[strLength-sufLength, sufLength], FALSE]]
    };

  EqualRS: PUBLIC PROC[r: Rope.ROPE, s: LONG STRING] RETURNS[BOOL] ~  TRUSTED {
    IF r.Length # s.length THEN RETURN[FALSE];
    FOR i: CARDINAL IN [0 .. s.length) DO
      IF r.Fetch[i] # s[i] THEN RETURN[FALSE];
      ENDLOOP;
    RETURN[TRUE]};

  EquivalentRS: PUBLIC PROC[r: Rope.ROPE, s: LONG STRING] RETURNS[BOOL] ~ TRUSTED {
    IF r.Length # s.length THEN RETURN[FALSE];
    FOR i: CARDINAL IN [0 .. s.length) DO
      IF Rope.Lower[r.Fetch[i]] # Rope.Lower[s[i]] THEN RETURN[FALSE];
      ENDLOOP;
    RETURN[TRUE]};


  StringToRope: PUBLIC UNSAFE PROC[from: LONG STRING]
      RETURNS[Rope.ROPE] ~ UNCHECKED {
    i: CARDINAL ← 0;
    
    EachChar: PROC RETURNS[c: CHAR] ~ TRUSTED {
      c ← from[i]; i ← i+1; RETURN};
      
    RETURN[Rope.FromProc[from.length, EachChar]]};
    
  
  CardFromRope: PUBLIC PROC[r: Rope.ROPE] RETURNS[val: LONG CARDINAL ← 0] ~ {
    FOR i: INT IN [0..r.Length) DO
      c: CHAR ~ r.Fetch[i];
      val ← 10*val + (c.ORD-'0.ORD);
      ENDLOOP;
    RETURN};

  RopeFromCard: PUBLIC PROC[val: LONG CARDINAL] RETURNS[Rope.ROPE] ~ {
    RETURN[IO.PutFR["%d", IO.card[val]]]};
    
    
  StampDigits: TYPE ~ [0 .. 4*TimeStamp.Stamp.SIZE);

  StampFromRope: PUBLIC PROC[r: Rope.ROPE] RETURNS[TimeStamp.Stamp] ~ {
    hex: PACKED ARRAY StampDigits OF [0..16);
    FOR i: NAT IN StampDigits DO
      c: CHAR ~ r.Fetch[i];
      hex[i] ← (SELECT c FROM
        IN ['0..'9] => (c.ORD-'0.ORD),
        IN ['a..'f] => (c.ORD-'a.ORD)+10,
        IN ['A..'F] => (c.ORD-'A.ORD)+10,
        ENDCASE => ERROR);
      ENDLOOP;
    RETURN[LOOPHOLE[hex]]};

  RopeFromStamp: PUBLIC PROC[stamp: TimeStamp.Stamp] RETURNS[Rope.ROPE] ~ {
    hex: PACKED ARRAY StampDigits OF [0..16) ~ LOOPHOLE[stamp];
    i: NAT ← 0;
    
    PutChar: PROC RETURNS[CHAR] ~ {
      d: [0..16) ~ hex[i];
      i ← i+1;
      RETURN[IF d<10 THEN VAL['0.ORD+d] ELSE VAL['a.ORD+(d-10)]]};
      
    RETURN[Rope.FromProc[StampDigits.LAST-StampDigits.FIRST+1, PutChar]]};
    

 -- misc
 
  Confirm: PUBLIC PROC[dch: CHAR, in, out: IO.STREAM] RETURNS[CHAR] ~ {
    ch: CHAR;
    bs: IO.STREAM ~ (IF in.backingStream = NIL THEN in ELSE in.backingStream);
    DO {
      ENABLE IO.Signal => {IF ec = Rubout THEN LOOP};
      out.Put[IO.string["? "L]];
      ch ← bs.GetChar;
      IF ch = '\n THEN ch ← dch;
      RETURN[Rope.Lower[ch]]};
      ENDLOOP;
    };

 -- npages should not include the leader page;  I'll add +1
  NewFile: PUBLIC UNSAFE PROC[name: Rope.Text, access: File.Permissions, npages: NAT]
      RETURNS[cap: File.Capability] ~ UNCHECKED {
    old: BOOL ← FALSE;
    IF access # CS.read THEN 
      cap ← Directory.CreateFile[LOOPHOLE[name], DCSFileTypes.tLeaderPage, npages + 1
		! Directory.Error => {
		    IF type = fileAlreadyExists THEN old ← TRUE 
		    ELSE ERROR FileError[$notFound];
		    CONTINUE}
		]
    ELSE old ← TRUE;
    IF old THEN 
      cap ← Directory.Lookup[fileName~LOOPHOLE[name], permissions~Directory.ignore
		! Directory.Error => {ERROR FileError[$notFound]}];
    cap ← Directory.UpdateDates[cap, access];
    IF old AND npages > 0 AND (access = CS.write OR access = CS.readWrite) THEN
      File.SetSize[cap, npages+1];
    };

  NewStream: PUBLIC UNSAFE PROC[name: Rope.Text, access: File.Permissions]
	RETURNS[Stream.Handle] ~ UNCHECKED {
    RETURN[FileStream.Create[NewFile[name, access, 0]]]};


 -- iostream utilities
 
  SetPFCodes: PUBLIC PROC[h: IO.STREAM] ~ {
    h.SetPFCodeProc['v, PrintVCode];
    h.SetPFCodeProc['y, PrintYCode]};

  PTimeStamp: TYPE ~ REF READONLY TimeStamp.Stamp;

  PrintVCode: IO.PFCodeProc ~ {
    WITH val SELECT FROM
      v: IO.Value.refAny => {
	pts: PTimeStamp ~ NARROW[v.value];
	stream.PutF["(%g#,%g#,%t)", IO.card[pts.net], IO.card[pts.host], IO.card[pts.time]]};
      ENDCASE => ERROR};

  PrintYCode: IO.PFCodeProc ~ {
    WITH val SELECT FROM
      v: IO.Value.cardinal => {
	hr, min, sec: CARDINAL;
	[min, sec] ← Inline.LongDivMod[v.value, 60];
	[hr, min] ← Inline.DIVMOD[min, 60];
	IF hr > 0 THEN
	  stream.PutF["%d:%02d:%02d", IO.card[hr], IO.card[min], IO.card[sec]]
	ELSE IF min > 0 THEN stream.PutF["%d:%02d", IO.card[min], IO.card[sec]]
	ELSE stream.PutF["%d", IO.card[sec]]};
      ENDCASE => ERROR};

 -- called when module is loaded
  Start: PROC ~ {
    z ← SafeStorage.NewZone[initialSize~40*Environment.wordsPerPage]};

 -- start code
  Start[];
  }.