-- CSImpl.Mesa, last edit February 13, 1983 7:07 pm
-- replacement for SubrImpl in the Cedar world

	DIRECTORY
	CS: TYPE USING [FileErrorType, PTimeStamp, 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, Handle, PFCodeProc, Put, PutChar, PutF, 
		SetPFCodeProc, Signal, string],
	Rope: TYPE USING[Fetch, Length, Lower, ROPE, Text],
	SafeStorage: TYPE USING[NewZone],
	Stream: TYPE USING[Handle];
	
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 PROC[to: LONG STRING, from: Rope.ROPE] = TRUSTED {
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;
};

SetPFCodes: PUBLIC PROC[h: IO.Handle] = {
h.SetPFCodeProc['a, PrintACode];
h.SetPFCodeProc['v, PrintVCode];
h.SetPFCodeProc['y, PrintYCode];
};

EndsIn: PUBLIC PROC[str: Rope.ROPE, suf: LONG STRING] RETURNS[BOOL] = TRUSTED {
strLength: CARDINAL ← str.Length[];
sufLength: CARDINAL ← suf.length;
FOR i: CARDINAL IN [0 .. strLength) DO {
	IF Rope.Lower[str.Fetch[i]] = Rope.Lower[suf[0]] THEN {
		FOR j: CARDINAL IN [1 ..sufLength) DO
			IF i+j >= strLength 
			OR Rope.Lower[str.Fetch[i+j]] ~= Rope.Lower[suf[j]] THEN 
				GOTO outer;
			ENDLOOP;
		IF i+sufLength < strLength THEN GOTO outer;
		RETURN[TRUE];
		};
	EXITS
	outer => NULL;
	};
	ENDLOOP;
RETURN[FALSE];
};

EqualRS: PUBLIC PROC[r: Rope.ROPE, s: LONG STRING] RETURNS[equal: 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[equivalent: 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];
	};

Confirm: PUBLIC PROC[dch: CHAR, in, out: IO.Handle] RETURNS[CHAR] =
	{
	ch: CHAR;
	DO
		{
		ENABLE IO.Signal => TRUSTED { IF ec = Rubout THEN LOOP};
		out.Put[IO.string["? "L]];
		ch ← in.GetChar[];
		IF ch = '\n THEN ch ← dch;
		ch ← Rope.Lower[ch];
		RETURN[ch];
		};
		ENDLOOP;
};

-- npages should not include the leader page;  I'll add +1
NewFile: PUBLIC PROC [name: Rope.Text, access: File.Permissions,
	npages: CARDINAL] RETURNS [cap: File.Capability] = TRUSTED {
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 PROC [name: Rope.Text, access: File.Permissions]
	RETURNS [Stream.Handle] = TRUSTED {
RETURN[FileStream.Create[NewFile[name, access, 0]]];
};

PrintACode: IO.PFCodeProc = TRUSTED {
WITH v: val SELECT FROM
refAny => {
	pts: CS.PTimeStamp ← NARROW[LOOPHOLE[v.value, REF ANY]];
	hex: PACKED ARRAY [0 .. 12) OF [0 .. 16) ← LOOPHOLE[pts↑];
	stream.PutChar['$];
	FOR i: CARDINAL IN [0 .. 12) DO
		stream.PutChar['a + hex[i]];
		ENDLOOP;
	};
ENDCASE => ERROR;
};

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

PrintYCode: IO.PFCodeProc = TRUSTED {
WITH v: val SELECT FROM
cardinal => {
	time: LONG CARDINAL ← v.value;
	hr, min, sec: CARDINAL;
	[min, sec] ← Inline.LongDivMod[time, 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
OnceInit: PROC = {
z ← SafeStorage.NewZone[initialSize: 40 * Environment.wordsPerPage];
};

-- start code
OnceInit[];
}.